• 寒假集训好题记录


    Day3B CF1012C(DP)

    题目大意:给你一个序列,定义第i个位置为山峰当且仅当h[i-1]和h[i+1]均小于h[i],现在可以减少某些h[i]的值,代价为减少的量的总和。对于1到n/2(向上取整)的每个数j,需要求出当序列里至少有j个山峰时,需要付出的最小代价

    考场再次弱智,我想个**的贪心,直接大力DP,再前缀和优化就行了

    定义f[i][j]为第i个是山峰,前i个点总共放了j个山峰的最小花费,那么i成为山峰需要协调h[i-1]和h[i+1]的值

    根据贪心的策略,如果h[i+1]大于等于h[i],那么我们一定把h[i+1]调整为h[i]-1。

    有了这个结论,我们就能确定当i-2是山峰时,h[i-1]被调整后的值,进而用于f[i]的转移

    直接DP的n3的,又由于第i-1个位置不与小于等于i-3的位置相关联,那么i-3之前的状态可以用前缀最大值优化!

    优化成了n2,轻松跑过

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long
     5 using namespace std;
     6 const int N1=2505;
     7 const int inf=0x3f3f3f3f;
     8 
     9 int n,m,ns;
    10 int a[N1*2],f[N1*2][N1],g[N1];
    11 
    12 int main()
    13 {
    14     scanf("%d",&n);
    15     for(int i=1;i<=n;i++) scanf("%d",&a[i]); 
    16     if(n==1){
    17         puts("0"); return 0;
    18     }
    19     memset(f,0x3f,sizeof(f)); memset(g,0x3f,sizeof(g));
    20     f[0][0]=0; f[1][0]=0; a[0]=a[1]+1;
    21     int ma;
    22     if(a[1]>a[2]) f[1][1]=0; else f[1][1]=a[2]-a[1]+1;
    23     for(int i=2;i<=n;i++)
    24     {
    25         ma=(i&1)?i/2+1:i/2;
    26         if(i==5)
    27             n=n;
    28         f[i][1]=max(0,a[i-1]-a[i]+1)+max(0,a[i+1]-a[i]+1);
    29         for(int j=2;j<=ma;j++)
    30         {
    31             if(a[i-1]<a[i-2]){
    32                 f[i][j]=f[i-2][j-1]+max(0,a[i-1]-a[i]+1);
    33             }else{
    34                 f[i][j]=f[i-2][j-1]+max(0,(a[i-2]-1)-a[i]+1);
    35             }
    36             if(i-3>=0)
    37             {
    38                 f[i][j]=min(f[i][j],g[j-1]+max(0,a[i-1]-a[i]+1));
    39             }
    40             if(i<n) f[i][j]+=max(0,a[i+1]-a[i]+1);
    41         }
    42         for(int j=1;j<=ma;j++) g[j]=min(g[j],f[i-2][j]);
    43     }
    44     for(int j=1;j<=ma;j++)
    45     {
    46         g[j]=min(g[j],min(f[n-1][j],f[n][j]));
    47         printf("%d ",g[j]);
    48     }
    49     puts("");
    50     return 0;
    51 }
    View Code

    Day3D CF862D(构造)

    妙妙题目

    解题必然从分治入手

    考虑先把0或1其中一个给找出来,然后再用分治的办法找另一个。

    前两次,问00……0和00……1,就能确定最后一位是0还是1了,现在假设最后一位是0,那么我们还需要找到1

    记录00……0的答案是x。再进行构造,1~mid是0,mid+1~r是1,记录这个询问序列的答案是y

    假设前后0和1的数量分别为l0,r0,l1,r1。

    那么x=l1+r1,y=l1+r0,又由于r0+r1=r-mid,因此可求出l1的值!

    根据l1的值决定向左区间或者右区间递归求解,最后一定能找到1

    注意如果不是第一层,询问还会带上非递归区间的贡献,用最初的x消一下就行了

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long
     5 using namespace std;
     6 const int N1=1005;
     7 const int inf=0x3f3f3f3f;
     8 
     9 int n,pos0,pos1;
    10 char str[N1];
    11 
    12 int l0,r0,l1,r1,x,y;
    13 void dfs1(int sum1,int l,int r)
    14 {
    15     if(l==r){ pos1=l; return; }
    16     int mid=(l+r)>>1,sum2,l1;
    17     for(int i=1;i<=n;i++) str[i]='0';
    18     for(int i=l;i<=mid;i++) str[i]='0';
    19     for(int i=mid+1;i<=r;i++) str[i]='1'; str[n+1]='
    ';
    20     printf("? %s",str+1); fflush(stdout);
    21     scanf("%d",&sum2); sum2=sum2-(x-sum1);
    22     l1=sum1+sum2-(r-mid); l1/=2;
    23     if(l1>0) dfs1(l1,l,mid);
    24     else dfs1(sum1,mid+1,r);
    25 }
    26 
    27 void dfs2(int sum1,int l,int r)
    28 {
    29     if(l==r){ pos0=l; return; }
    30     int mid=(l+r)>>1,sum2,l1;
    31     for(int i=1;i<=n;i++) str[i]='1';
    32     for(int i=l;i<=mid;i++) str[i]='1';
    33     for(int i=mid+1;i<=r;i++) str[i]='0'; str[n+1]='
    ';
    34     printf("? %s",str+1); fflush(stdout);
    35     scanf("%d",&sum2); sum2=sum2-(x-sum1);
    36     l1=sum1+sum2-(r-mid); l1/=2;
    37     if(l1>0) dfs2(l1,l,mid);
    38     else dfs2(sum1,mid+1,r);
    39 }
    40 
    41 int main()
    42 {
    43     // freopen("a.in","r",stdin);
    44     scanf("%d",&n);
    45     for(int i=1;i<=n;i++) str[i]='0'; str[n+1]='
    ';
    46     printf("? %s",str+1); fflush(stdout);
    47     scanf("%d",&x); 
    48     str[n]='1';
    49     printf("? %s",str+1); fflush(stdout);
    50     scanf("%d",&y); 
    51     if(x<y){
    52         pos0=n; 
    53         dfs1(x,1,n-1);
    54     }else{
    55         pos1=n;
    56         for(int i=1;i<=n;i++) str[i]='1'; str[n+1]='
    ';
    57         printf("? %s",str+1); fflush(stdout);
    58         scanf("%d",&x); 
    59         dfs2(x,1,n-1);
    60     }
    61     printf("! %d %d
    ",pos0,pos1);
    62     return 0;
    63 }
    View Code

    Day3H CF1060D(贪心)

    题目大意:n个人,围成任意个闭环,对于每个人,要求他左手和右手边分别至少有l[i]和r[i]个空凳子。如果一个人围成环,那么他左右两边是交叠在一起的。现在问最少需要的凳子数量

    把每个人的左右手分开来看,如果我们给每个左手分配一个独特的右手,那么左手和右手构成一一映射。放到图上,这一方法可以理解为一条边只能被两个人分别用左侧和右侧用,比较显然地成立了

    那么如何分配是最优策略呢?考虑对左手右手分别排序,那么把第i个左手和第i个右手相对应是最优的。

    考虑交换两个左手/右手,代价一定比原来大……

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long
     5 using namespace std;
     6 const int N1=100005;
     7 const int inf=0x3f3f3f3f;
     8 
     9 int n,m;
    10 int l[N1],r[N1];
    11 
    12 int main()
    13 {
    14     scanf("%d",&n);
    15     int x,y,v; ll ans=0;
    16     for(int i=1;i<=n;i++) 
    17     {
    18         scanf("%d%d",&x,&y);
    19         if(x==y) ans+=x;
    20         else m++, l[m]=x, r[m]=y;
    21     }
    22     sort(l+1,l+m+1); sort(r+1,r+m+1);
    23     for(int i=1;i<=m;i++) ans+=max(l[i],r[i]);
    24     printf("%lld
    ",ans+n);
    25     return 0;
    26 }
    View Code

    Day3I CF1060E(树上计数)

    奇数长度路径变为x/2+1,偶数长度路径变为x/2

    最终答案=(所有路径总长度-变化前奇数路径总数目)/2

    总长度在树上数就行,奇数长度的路径数目呢?

    dis(x,y)=dep[x]+dep[y]-2*dep[lca(x,y)]

    减掉的lca不影响奇偶性!因此直接根据dep数组就能求出这个数目了

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long
     5 using namespace std;
     6 const int N1=200005;
     7 const int inf=0x3f3f3f3f;
     8 
     9 int n;
    10 int sz[N1],dep[N1];
    11 ll sdis[N1],sum[N1];
    12 struct Edge{
    13 int to[N1*2],nxt[N1*2],head[N1],cte;
    14 void ae(int u,int v)
    15 { cte++; to[cte]=v; nxt[cte]=head[u]; head[u]=cte; }
    16 }e;
    17 
    18 ll dfs(int u,int ff)
    19 {
    20     ll ans=0; sz[u]=1;
    21     for(int j=e.head[u];j;j=e.nxt[j])
    22     {
    23         int v=e.to[j]; if(v==ff) continue;
    24         dep[v]=dep[u]+1; ans+=dfs(v,u);
    25         ans+=(sdis[u]+sz[u])*sz[v]+sdis[v]*sz[u];
    26         sz[u]+=sz[v]; sdis[u]+=sdis[v]+sz[v];
    27     }
    28     return ans;
    29 }
    30 
    31 int main()
    32 {
    33     scanf("%d",&n);
    34     int x,y; 
    35     for(int i=1;i<n;i++) scanf("%d%d",&x,&y), e.ae(x,y), e.ae(y,x);
    36     ll ans=dfs(1,0),s0=0,s1=0,tot=0;
    37     for(int i=1;i<=n;i++) 
    38     {
    39         sum[dep[i]]++;
    40         if(dep[i]&1) s1++; else s0++;
    41     }
    42     for(int i=0;i<n;i++)
    43     {
    44         if(i&1) tot+=1ll*sum[i]*s0;
    45         else tot+=1ll*sum[i]*s1;
    46     }
    47     tot>>=1;
    48     ans=(ans+tot)>>1;
    49     printf("%lld
    ",ans);
    50     return 0;
    51 }
    View Code

    Day4A LOJ2880(CDQ+二分+单调栈)

    注意这个一个计数型分治问题,可以左区间——右区间——当前区间——排序

    而DP型分治问题需要左区间——当前区间——右区间——排序

    这是由于右区间内点的最优解可能需要左区间提供,而计数型直接无脑计数就行了

    此外,第一种情况时间更优且更好写,因为避免了处理当前区间时还需要按y来sort的代价

    直接sort时间会变成log方,有些情况下只能桶排,而不连续的桶排不好写

    然后是题解:

    我们先把点按x排序进行分治,回溯时按y排序

    按y从小到大依次处理右侧区间的每个点i,左侧所有y小于i的点都可能产生贡献

    画图可知,左侧的合法点仅有那些最靠右上的一排点:且满足x递增时,y递减

    我们利用这个性质构造左侧的x递减的单调栈

    然而右侧的点也可能相互影响,我们已经把右侧的点按y从小到大排好序,那么实际上对当前点i产生影响的,只有在i左边且y最大的那一个点!

    我们再搞一个单调栈维护右边的点,这是一个x递增的单调栈。每次需要刨除那个点的贡献,在单调栈上二分

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long 
     5 using namespace std;
     6 const int N1=200005;
     7 const int inf=0x3f3f3f3f;
     8 
     9 int T,n;
    10 
    11 struct node{ ll x,y; }a[N1],tmp[N1];
    12 int cmp1(node aa,node bb){ return aa.x<bb.x; }
    13 int cmp2(node aa,node bb){ return aa.y<bb.y; }
    14 int sta[N1],ta,stb[N1],tb;
    15 void pusha(int i)
    16 {
    17     while(ta>0)
    18     {
    19         if(a[i].x>a[sta[ta]].x) ta--;
    20         else break;
    21     }
    22     sta[++ta]=i;
    23 }
    24 void pushb(int i)
    25 {
    26     while(tb>0)
    27     {
    28         if(a[i].x<a[stb[tb]].x) tb--;
    29         else break;
    30     }
    31     stb[++tb]=i;
    32 }
    33 ll check()
    34 {
    35     if(tb==1) return ta;
    36     int i=stb[tb],j=stb[tb-1], l=1,r=ta,mid,ans=0;
    37     while(l<=r)
    38     {
    39         mid=(l+r)>>1;
    40         if(a[sta[mid]].y>a[j].y) ans=mid, r=mid-1;
    41         else l=mid+1;
    42     }
    43     if(!ans) return 0;
    44     return ta-ans+1;
    45 }
    46 void clr(){ while(ta) sta[ta--]=0; while(tb) stb[tb--]=0; }
    47 ll CDQ(int L,int R)
    48 {
    49     if(L==R) return 0;
    50     int mid=(L+R)>>1; ll ans=0;
    51     ans+=CDQ(L,mid);
    52     ans+=CDQ(mid+1,R);
    53     int l=L,r=mid+1,pos=L;
    54     if(L==1&&R==5)
    55         n=n;
    56     for(;r<=R;r++)
    57     {
    58         for(;a[l].y<a[r].y&&l<=mid;l++) pusha(l);
    59         pushb(r);
    60         ans+=check();
    61     }
    62     clr();
    63     for(l=L,r=mid+1;l<=mid&&r<=R;)
    64     {
    65         if(a[l].y<a[r].y) tmp[pos]=a[l], l++,pos++;
    66         else tmp[pos]=a[r], r++,pos++;
    67     }
    68     while(l<=mid) tmp[pos]=a[l], l++,pos++;
    69     while(r<=R) tmp[pos]=a[r], r++,pos++;
    70     for(int i=L;i<=R;i++) a[i]=tmp[i];
    71     return ans;
    72 }
    73 
    74 int main()
    75 {
    76     scanf("%d",&n);
    77     for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
    78     sort(a+1,a+n+1,cmp1);
    79     ll ans=CDQ(1,n);
    80     printf("%lld
    ",ans);
    81     return 0;
    82 }
    83 
    84 
    85  
    View Code

    Day4F CF1068D(序列计数)

    现在给你一个序列a,其中的有些位置的值是已知的,有些的未知的,现在要求填满序列a,但必须满足

    a[1]a[2]

    a[n]a[n-1]

    a[i]max(a[i1],a[i+1]for all i from 2 to n-1

    问有多少种填的方案

    之前一通乱想。。结果都是假的,把题面理解错了

    定义f[i][j][0/1]表示第i个位置填数字j,且i-1否/能使得a[i]a[i1]成立时,的方案数

    递推式真的好想,然后前缀和优化一下就行了

    tips:

    这种序列上有左右相邻元素限制的题目,往往可以用小状态表示是否满足限制,再进行转移。一些树形DP也是类似的思路(比如点覆盖边覆盖)

    还有一些题目需要正着计数再反着计数,再在某个位置碰上将两个数组组合起来。但这时候要注意如果相邻元素相同时可能会重复计数,需要仔细考虑

    还有一些会用到FFT?好吧我多项式实在是拉胯

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define ll long long 
     5 using namespace std;
     6 const int N1=100005;
     7 const int M1=203;
     8 const int inf=0x3f3f3f3f;
     9 const int p=998244353;
    10 const int maxn=200;
    11 
    12 int n;
    13 int a[N1];
    14 int f[N1][M1][2],sf[N1][M1][2];
    15 
    16 int main()
    17 {
    18     scanf("%d",&n);
    19     //n==1
    20     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    21     //f
    22     for(int j=1;j<=maxn;j++) sf[0][j][1]=1;
    23     for(int i=1;i<=n;i++)
    24     {
    25         if(a[i]==-1){
    26             for(int j=1;j<=maxn;j++) 
    27             {
    28                 f[i][j][0]=(sf[i-1][j-1][0]+sf[i-1][j-1][1])%p;
    29                 sf[i][j][0]=(sf[i][j-1][0]+f[i][j][0])%p;
    30             }
    31             for(int j=1;j<=maxn;j++) 
    32             {
    33                 f[i][j][1]=(1ll*f[i-1][j][0]+sf[i-1][maxn][1]-sf[i-1][j-1][1]+p)%p;
    34                 sf[i][j][1]=(sf[i][j-1][1]+f[i][j][1])%p;
    35             }
    36         }else{
    37             int j=a[i];
    38             f[i][j][0]=(sf[i-1][j-1][0]+sf[i-1][j-1][1])%p;
    39             f[i][j][1]=(1ll*f[i-1][j][0]+sf[i-1][maxn][1]-sf[i-1][j-1][1]+p)%p;
    40             for(j=1;j<=maxn;j++) sf[i][j][0]=(sf[i][j-1][0]+f[i][j][0])%p;
    41             for(j=1;j<=maxn;j++) sf[i][j][1]=(sf[i][j-1][1]+f[i][j][1])%p;
    42         }
    43     }
    44     ll ans=sf[n][maxn][1];
    45     printf("%lld
    ",ans);
    46     return 0;
    47 }
    View Code

    Day7I 平面点集计数(线段树逆序对)

    求满足条件的点集:1.有且仅有3个点 2.x,y都各不相同 3.按x从小到大分为左中右三个点,需要满足y1>y2且y1<y3

    考场上并没有想出很好的做法,分治想了好久..下考dkr告诉我用线段树就可以了!

    左和中这两个点的关系可以看成逆序对!x为位置,y为权值

    从左到右依次枚举每个点作为最右侧的点,求出y小于它的点的逆序对总数就行了

    按y为方向开一个线段树,记录size表示点的数量,再记录sum表示逆序对数量之和。

    每插入一个点,所有y比它大的点都能和它形成逆序对,利用size区间修改sum!

     1 #include <cmath>
     2 #include <vector>
     3 #include <cstdio>
     4 #include <cstring>
     5 #include <algorithm>
     6 #define ll long long 
     7 #define dd long double
     8 using namespace std;
     9 const int N1=100005;
    10 const dd eps=1e-9;
    11 const int inf=0x3f3f3f3f;
    12 
    13 int n,m;
    14 struct node{int x,y;}a[N1],b[N1];
    15 int cmp1(node s1,node s2){ return s1.x<s2.x; }
    16 int cmp2(node s1,node s2){ return s1.y<s2.y; }
    17 
    18 struct SEG{
    19 int sz[N1<<2],tag[N1<<2]; ll sum[N1<<2];
    20 void pushdown(int rt)
    21 {
    22     if(!tag[rt]) return;
    23     sum[rt<<1]+=1ll*tag[rt]*sz[rt<<1]; 
    24     sum[rt<<1|1]+=1ll*tag[rt]*sz[rt<<1|1];
    25     tag[rt<<1]+=tag[rt]; tag[rt<<1|1]+=tag[rt]; tag[rt]=0;
    26 }
    27 void pushup(int rt)
    28 {
    29     sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    30     sz[rt]=sz[rt<<1]+sz[rt<<1|1];
    31 }
    32 ll query(int L,int R,int l,int r,int rt)
    33 {
    34     if(L<=l&&r<=R) return sum[rt];
    35     int mid=(l+r)>>1; ll ans=0;
    36     pushdown(rt);
    37     if(L<=mid) ans+=query(L,R,l,mid,rt<<1);
    38     if(R>mid) ans+=query(L,R,mid+1,r,rt<<1|1);
    39     return ans;
    40 }
    41 void upd(int L,int R,int l,int r,int rt,int w)
    42 {
    43     if(L<=l&&r<=R){ sum[rt]+=1ll*w*sz[rt]; tag[rt]+=w; return; }
    44     int mid=(l+r)>>1;
    45     pushdown(rt);
    46     if(L<=mid) upd(L,R,l,mid,rt<<1,w);
    47     if(R>mid) upd(L,R,mid+1,r,rt<<1|1,w);
    48     pushup(rt);
    49 }
    50 void ins(int x,int l,int r,int rt)
    51 {
    52     if(l==r){ sz[rt]++; return; }
    53     int mid=(l+r)>>1;
    54     pushdown(rt);
    55     if(x<=mid) ins(x,l,mid,rt<<1);
    56     else ins(x,mid+1,r,rt<<1|1);
    57     pushup(rt);
    58 }
    59 }s;
    60 
    61 int main()
    62 {
    63     freopen("a.in","r",stdin);
    64     scanf("%d",&n);
    65     for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&b[i].y), b[i].x=i;
    66     sort(b+1,b+n+1,cmp2);
    67     for(int i=1;i<=n;i++)
    68     {
    69         if(b[i].y!=b[i-1].y) m++;
    70         a[b[i].x].y=m;
    71     }
    72     sort(a+1,a+n+1,cmp1);
    73     ll ans=0; int i,j;
    74     for(i=1;i<=n;)
    75     {
    76         for(j=i;a[j].x==a[i].x;j++)
    77         {
    78             if(a[j].y>1) ans+=s.query(1,a[j].y-1,1,m,1);
    79         }
    80         for(j=i;a[j].x==a[i].x;j++)
    81         {
    82             if(a[j].y<=m) s.upd(a[j].y+1,m,1,m,1,1);
    83         }
    84         for(j=i;a[j].x==a[i].x;j++)
    85         {
    86             s.ins(a[j].y,1,m,1);
    87         }
    88         i=j;
    89     }
    90     printf("%lld
    ",ans);
    91     return 0;
    92 }
    View Code
  • 相关阅读:
    you must restart adb and eclipse的相关解决办法
    Qt slot中获取sender
    添加开机启动项
    Unreal开发HTC Vive程序,开启VR编辑模式
    Android弹出一项权限请求
    Unreal新建C++类或C++项目失败
    win10 设置C盘访问权限
    windows系统共享设置最顺的一次
    下载Qt安装包
    单例模式
  • 原文地址:https://www.cnblogs.com/guapisolo/p/14305472.html
Copyright © 2020-2023  润新知