寒假集训好题记录
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 }
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 }
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 }
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 }
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
Day4F CF1068D(序列计数)
现在给你一个序列a,其中的有些位置的值是已知的,有些的未知的,现在要求填满序列a,但必须满足
a[1]≤a[2]
a[n]≤a[n-1]
a[i]≤max(a[i−1],a[i+1]) for all i from 2 to n-1
问有多少种填的方案
之前一通乱想。。结果都是假的,把题面理解错了
定义f[i][j][0/1]表示第i个位置填数字j,且i-1否/能使得a[i]≤a[i−1]成立时,的方案数
递推式真的好想,然后前缀和优化一下就行了
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 }
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 }