寒假集训好题记录

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]='\n';
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]='\n';
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]='\n';
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]='\n';
57         printf("? %s",str+1); fflush(stdout);
58         scanf("%d",&x); 
59         dfs2(x,1,n-1);
60     }
61     printf("! %d %d\n",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\n",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\n",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最大的那一个点!也就是单调栈上的前一个点。所有i不合法的情况,都一定包含这个点。可以通过画图验证

我们再搞一个单调栈维护右边的点,这是一个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\n",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\n",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\n",ans);
91     return 0;
92 }
View Code

 

posted @ 2021-01-20 22:22  guapisolo  阅读(108)  评论(0编辑  收藏  举报