杂题
不知道扔哪的题就扔这
大概是个乱搞专题(
CF1651D
给你$n$个点,对每个点找曼哈顿距离最近的没有被占用的点
sol:
考虑到一个点最近的空点的出现
考虑从左往右,我们贪心的想,我们肯定是一直往四个方向bfs,直到遇到一个空点为止
但是如果对每个有的点都这么做一遍显然会T飞
于是我们反过来做,考虑用空点去扩展。
考虑到空点附近一定有一个有的点
所以可以从周围有空点的有的点开始,遍历所有有的点,做一遍bfs即可。
#include<bits/stdc++.h> using namespace std; int dx[]={0,1,-1,0,0}; int dy[]={0,0,0,1,-1}; pair<int,int>ans[200005]; queue<pair<int,int> >bfs; pair<int,int> a[200005]; bool vis[200005]; map<pair<int,int>,int>mp; int N; int main(){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d%d",&a[i].first,&a[i].second); mp[a[i]]=i; } for (int i=1;i<=N;i++){ for (int j=1;j<=4;j++){ int x=a[i].first+dx[j],y=a[i].second+dy[j]; pair<int,int> nww={x,y}; if (!mp[nww]) { vis[i]=1; ans[i]=nww; break; } } if (vis[i]==1) bfs.push(a[i]); } while (!bfs.empty()){ pair<int,int> s=bfs.front(); bfs.pop(); if (!mp[s]) continue; int x=mp[s]; for (int j=1;j<=4;j++){ pair<int,int> nww; nww={s.first+dx[j],s.second+dy[j]}; if (mp[nww] && !vis[mp[nww]]){ vis[mp[nww]]=1; ans[mp[nww]].first=ans[mp[s]].first; ans[mp[nww]].second=ans[mp[s]].second; bfs.push(nww); } } } for (int i=1;i<=N;i++) printf("%d %d\n",ans[i].first,ans[i].second); return 0; }
CF1668D
划分问题,有个最暴力的$dp$
$dp_{i,j}$表示考虑到第$i$位,划分了$j$段的方案
转移显然
发现在这题里,划分段数没有约束
于是状态修改为$dp_i$表示第$i$个位置结尾的最优
转移就是
$dp_r = max{dp[l] + (r-l+1)} (sum_r > sum_{l-1}) $
$dp_r = max(dp[l] - (r-l+1) (sum_r < sum_{l-1})$
$dp_r = max{dp_l} sum[r]==sum[l-1]$
然后把有$l$的部分扔到一起去,会发现其实只要找后缀$sum_i - i $和前缀$sum_i + i$的最值
这个东西离散化树状数组一下就解决了。
#include<bits/stdc++.h> using namespace std; int Lowbit(int x){return (x&(-x));} int mx; int dp[1000005]; int Ans[1000005]; struct Node{ int Tree[1000005]; int Add(int x,int y){for (int i=x;i<=mx;i+=Lowbit(i)) Tree[i]=max(Tree[i],y);} int query(int x){int ans=-1e9; for (int i=x;i;i-=Lowbit(i)) ans=max(ans,Tree[i]); return ans;} void Clear(){for (int i=1;i<=mx;i++) Tree[i]=-1e9; } }Tree1,Tree2; long long Sum[1000005]; long long a[1000005],b[1000005]; int main(){ int T; cin>>T; while (T--){ int N; cin>>N; mx=N<<1; Tree1.Clear(),Tree2.Clear(); for (int i=1;i<=N;i++) Ans[i]=-1e9,dp[i]=-1e9; for (int i=1;i<=N;i++){ scanf("%lld",&a[i]); b[i]=a[i]; Sum[i]=Sum[i-1]+a[i]; a[i]=Sum[i]; } //for (int i=1;i<=N;i++){ // cout<<Sum[i]<<" "; //} //cout<<endl; sort(Sum+1,Sum+N+1); int N1=unique(Sum+1,Sum+N+1)-Sum-1; for (int i=1;i<=N;i++){ a[i]=lower_bound(Sum+1,Sum+N1+1,a[i])-Sum; } long long Summ=0; for (int i=1;i<=N;i++){ dp[i]=Tree1.query(a[i]-1)+i; //cout<<a[i]-1<<" "; //cout<<Tree1.query(a[i]-1)<<endl; dp[i]=max(dp[i],Tree2.query(N1-a[i])-i); dp[i]=max(dp[i],Ans[a[i]]); Summ+=b[i]; if (Summ==0) dp[i]=max(dp[i],0); else if (Summ>0) dp[i]=max(dp[i],i); else dp[i]=max(dp[i],-i); Tree1.Add(a[i],dp[i]-i); Tree2.Add(N1-a[i]+1,dp[i]+i); Ans[a[i]]=max(Ans[a[i]],dp[i]); } cout<<dp[N]<<endl; } return 0; }
$CF1701D$
首先一个比较显然的推论,每个点可以放置的数字范围是可以直接算出来的
现在问题就转化成,把$1~n$这$n$个数字放进$n$个坑里,使得每个坑都满足条件
把每个点的范围扔到数轴上,然后从$1~n$的去考虑放置这些数字
那么我们贪心的想,一定是优先解决限制的比较死的区间是比较优秀的
也就是说,在一个数字同时能满足多个区间的时候,它一定优先满足那个右端点最靠左的
这东西拉个小根堆维护就好了。
#include<bits/stdc++.h> using namespace std; int T,N; struct Node{ int L,pos; }a[500005]; int ans[500005],b[500005]; int temp(Node a,Node b){ return (a.L<b.L); } priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que; int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&b[i]); a[i].L=i/(b[i]+1)+1; a[i].pos=i; } sort(a+1,a+N+1,temp); int nw=1; for (int i=1;i<=N;i++){ while (nw<=N&&a[nw].L<=i){ int id=a[nw].pos; if (b[id]!=0) que.push({id/b[id],id}); else que.push({N,id}); nw++; } pair<int,int> As=que.top(); que.pop(); ans[As.second]=i; } for (int i=1;i<=N;i++) cout<<ans[i]<<" "; cout<<endl; } return 0; }
$CF1695D2$
老年人智商不行了
首先可以明确一个点,显然取全部的叶子节点一定可以确定所有的$x$,因为如果把树拆成一堆链的话,叶子节点就是它的链头和链尾,那么对于任意一条链,知道链头和链尾就能知道整个树
现在的问题是:显然有一部分叶子节点是不必要的,那么要怎么找到那些不必要的点呢
考虑一个多分叉的树,如果它有两个及以上的子树内不存在关键点的话,那么这两个子树就一定不能被分辨出来,因为他们到剩下的子树下所有点的距离是相等的。
于是,反过来说,对于一个有两个以上子树的点,这个点只能在一个子树内节省一个关键点。(*)
那么其实,就可以理解为,先把所有叶子节点选上,然后能省则省
具体做法就是,从每个度数为$1$的节点不断暴力的跳,直到遇到第一个有多分叉的点,并且做标记。如果到这个点的时候,这个点还没被做过标记的话,就说明这个叶子节点可以作为被节省的那个点,就不用$+1$,然后大力$dfs$就好了。
#include<bits/stdc++.h> using namespace std; int N; int T; bool vis[200005]; int d[200005],ans; vector<int> E[200005]; void dfs(int Now,int Fa){ for (auto v:E[Now]){ if (v==Fa) continue; if (d[v]<=2) dfs(v,Now); else{ if (vis[v]) ans++; vis[v]=true; return; } } } int main(){ cin>>T; while (T--){ ans=0; scanf("%d",&N); if (N==1) { printf("0\n"); continue; } for (int i=1;i<=N;i++){ E[i].clear(); vis[i]=false; d[i]=0; } for (int i=1;i<N;i++){ int x,y; scanf("%d%d",&x,&y); E[x].push_back(y); E[y].push_back(x); d[x]++; d[y]++; } for (int i=1;i<=N;i++){ if (d[i]==1){ dfs(i,i); } } if (ans==0) ans++; printf("%d\n",ans); } return 0; }
CF1696D
很奇妙的一个题(
考虑对于一个区间来说,一个点$mx$,一个点$mn$
不妨设$mx$在左边,$mn$在右边
$mx$左边的点一定没有路径链到$mx$右边
因为如果要链到$mx$右边的话,就说明左边或者右边有一个点比$mx$更大
那么同理,$mn$也是
那么就会发现,走$mx$到$mn$一定是最优的
于是我们就把$[1,n]$分成了$[1,mx]$和$[mx,mn]$和$[mn,N]$
于是我们就可以直接往下分治了
因为这样每次总长度至少减少$1$,所以最多是$n$次
至于最大最小值的位置,随便拉个数据结构维护就好了。
#include <bits/stdc++.h> using namespace std; int pos[250005],Treemx[4*250060],a[250005],Treemn[4*250060],N; void Build(int Now,int l,int r){ if (l==r){ Treemx[Now]=a[l]; Treemn[Now]=a[r]; return; } int mid=(l+r)>>1; Build(Now<<1,l,mid); Build(Now<<1|1,mid+1,r); Treemx[Now]=max(Treemx[Now<<1],Treemx[Now<<1|1]); Treemn[Now]=min(Treemn[Now<<1],Treemn[Now<<1|1]); } int GetMn(int Now,int l,int r,int L,int R){ if (L<=l&&r<=R) return Treemn[Now]; int mid=(l+r)>>1; int ans=1e9; if (L<=mid) ans=min(ans,GetMn(Now<<1,l,mid,L,R)); if (mid<R) ans=min(ans,GetMn(Now<<1|1,mid+1,r,L,R)); return ans; } int GetMx(int Now,int l,int r,int L,int R){ if (L<=l&&r<=R) return Treemx[Now]; int mid=(l+r)>>1; int ans=0; if (L<=mid) ans=max(ans,GetMx(Now<<1,l,mid,L,R)); if (mid<R) ans=max(ans,GetMx(Now<<1|1,mid+1,r,L,R)); return ans; } int Getans(int l,int r){ if (l>=r) return 0; if (l+1==r) return 1; int mn=GetMn(1,1,N,l,r); int mx=GetMx(1,1,N,l,r); int L=pos[mn],R=pos[mx]; if (L>R) swap(L,R); return Getans(l,L)+Getans(R,r)+1; } int main(){ int T; cin>>T; while(T--){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&a[i]); pos[a[i]]=i; } //cout<<"qwq"<<endl; Build(1,1,N); printf("%d\n",Getans(1,N)); } return 0; } /* 1 10 7 4 8 1 6 10 3 5 2 9 */
CF1689D
开始看到最大值最小想到的是二分
然后发现二分完,每个点占据的区域是一个斜的正方形
然后想到转45°坐标
然后想到转切比雪夫距离
切比雪夫距离是最大的$max(|xi-x|,|yi-y|)$
发现这个新图可以把$x$和$y$拆开讨论,然后就是最大的最大,直接取全部里的最大就好了。
转完之后取$max$操作可以分开算,然后找到每个黑点,暴力找到这个新图的黑点边界,暴力枚举每个点计算最大的$x$向距离和$y$向距离就好了。
#include <bits/stdc++.h> using namespace std; int N,M; char c[5005][5005]; int T; int A,B,C,D; int main(){ cin>>T; while (T--){ scanf("%d%d",&N,&M); A=-1e9,B=1e9+7,C=-1e9,D=1e9+7; for (int i=1;i<=N;i++) for (int j=1;j<=M;j++){ cin>>c[i][j]; if (c[i][j] == 'B'){ int nx=i+j,ny=i-j; A=max(nx,A); B=min(nx,B); C=max(ny,C); D=min(ny,D); } } //cout<<A<<" "<<B<<" "<<C<<" "<<D<<endl; int ans=1e9+7,X=0,Y=0; for (int i=1;i<=N;i++) for (int j=1;j<=M;j++){ int nx=i+j,ny=i-j; int dis=max(max(abs(ny-C),abs(ny-D)),max(abs(nx-A),abs(nx-B))); //cout<<dis<<" "<<ans<<endl; if (dis<ans){ ans=dis; X=i; Y=j; } } printf("%d %d\n",X,Y); } return 0; }
cf1717D
数论题
把那个$lcm$写出来
设$g$为$gcd(a,b)$
$lcm(c,g) = \frac{c*g}{(c,g)}$
显然,$a$和$b$地位同等,$c$比较特殊,于是我们考虑枚举一下$c$
当枚举完$c$之后,我们就会发现有$a+b = n-c$成立
那么,不妨设$(a,b) = k$,同时$a+b = n-c$成立
假装我们已经知道$k$了,那么我们只需要知道有多少组$k$就可以算答案。
设$n-c = t$
则有$(a,t - a) = k$
套路化的两边除个$k$
$(\frac{a}{k},\frac{t-a}{k}) = 1$
根据这个式子,可以知道一件事:
$a$是$k$的倍数,同时$t-a$也是$k$的倍数
通过这两个条件可以发现,$t$是$k$的倍数(同余)
于是我们就把$k$的枚举条件缩小到了$t$的所有约数里
但是我们还需要知道有多少组这样的$k$
设$\frac{a}{k} = m$
$(m,\frac{t}{k} - m) = 1$
$(m,x-m) = 1$,$x$是常数
$(m,x-m) = (m,x) = 1$
所以即等价于$phi(x)$,即$phi(\frac{t}{k})$
#include <bits/stdc++.h> using namespace std; const long long fish = 1e9+7; vector<long long> Div[100005]; int IsPrime[100005],Prime[100005],Index,phi[100005]; void Pre(int mx){ phi[1] = 1; for (int i = 2 ; i <= mx ; i++){ if (!IsPrime[i]) {Prime[++Index] = i;phi[i] = i - 1;} for (int j = 1;j<=Index && i * Prime[j] <= mx ; j++){ IsPrime[i*Prime[j]] = true; if (i%Prime[j] == 0){ phi[i*Prime[j]] = phi[i] * Prime[j]; } else phi[i*Prime[j]] = phi[i] * (Prime[j] - 1); } } for (int i=1;i<=mx;i++) for (int j = i ; j <= mx ; j +=i){ Div[j].push_back(i); } phi[1] = 0; } int main(){ int N; cin>>N; Pre(N); long long ans = 0; for (long long c = 1 ; c <= N; c++){ int x = N-c; for (auto g : Div[x]){ long long as = 1ll*g*c/__gcd(g,c); long long times = phi[x/g]; ans = (ans + as*times)%fish; } } cout<<ans; return 0; }
CF1749D
发现一定有一种答案是{1,1,1,1,1.....}
于是问题就变成:如何找到一种{1,1,1,1,1....}以外的合法方案
于是问题就变成:
对于一个位置$i$,因为它在{1,1,1,1,1}中会遍历$1..i$的所有数字
也就是说只要存在一个数$x$在$1$到$i$之间,有$(x,a_i) = 1$
就意味着一定有另一组解
也就是说该数字和任何一个数互素就好
但是这个显然不太好讨论
于是我们就讨论它与前面所有数都不互素
于是就包含前面的所有素因子,然后容斥计数一下就好了。
边扫边求答案就好
#include <bits/stdc++.h> using namespace std; const int mx = 3e5; const long long fish = 998244353; int IsPrime[mx+5],f[mx+5]; int main(){ long long N,M; for (int i = 2 ; i <= mx ; i ++) for(int j = i+i ; j <= mx ; j +=i) IsPrime[j] = 1; cin>>N>>M; long long nw = 1,nw1=1,ans2=1,ans=1,ans3=0; for (int i = 2 ; i <= N ; i ++){ //-1337 424242424242 //cout<<nw<<endl; if (!IsPrime[i] && nw <= M){ nw = 1ll*nw * i; if (nw > M) {ans = 0;} nw1 = M/nw; } long long y = nw1 % fish,z=M%fish; ans = 1ll* ans%fish * 1ll * y%fish; ans2 = 1ll* ans2 * 1ll * z % fish; ans3 = (ans3 + 1ll*z*((ans2-ans+fish)%fish)%fish)%fish; //cout<<ans<<" "<<ans2<<" "<<ans3<<" "<<nw<<" "<<nw1<<" "<<y<<endl; } cout<<ans3%fish; return 0; }
CF1754E
我怎么就不会概率题啊呜呜
感觉是很经典的模型
参考了官方题解和评论区的题解。
发现最后的序列一定是$00000.....11111$的形式
然后发现该题给出的交换方式,前$cnt_0$个$0$一定是不会被交换到后面去的
于是就可以根据这个划分状态。
设$f[i]$表示当前还有几个$0$不在前$cnt_0$个
那么,考虑某次交换会导致一个0会被换到前$cnt_0$个的概率
$p = \frac{i*i}{\frac{N*(N-1)}{2}}$(因为前面$i$个$1$一定有$0$和它配对,那么前后一共可以产生$i*i$组有价值的交换,剩下的就是$C_N^2$
于是有了$p$之后,就考虑$f$的转移
$f[i] = f[i+1]*p + f[i]*(1-p)$
移项,发现$f[i] = f[i+1] + \frac{1}{p}$
递推即可。
#include<bits/stdc++.h> using namespace std; int N; int T; long long dp[200005]; const long long fish = 998244353; long long Pow(long long x,int y){ long long ans = 1; for (;y;y>>=1){ if (y&1) ans = 1ll * ans * x%fish; x = 1ll * x * x %fish; } return ans; } int a[200005]; int main(){ cin>>T; while (T--){ cin>>N; for (int i = 1; i <= N ; i ++) cin>>a[i]; int cnt0=0; for (int i = 1; i <= N ; i ++) if (a[i] == 0) cnt0 ++; int cnt00 = 0; for (int i = 1 ; i <= N ; i ++) if (a[i] == 0 && i > cnt0) cnt00 ++; dp[cnt00] = 0; long long inv = 1ll*N*(N-1)%fish*Pow(2,fish-2)%fish; for (int i = cnt00-1 ; i >= 0 ; i--){ dp[i] = (dp[i+1]+1ll * inv * Pow(1ll*(i+1)*1ll*(i+1)%fish,fish-2)%fish)%fish; //cout<<dp[i]<<endl; } cout<<dp[0] <<'\n'; } return 0; }
cf1646D
套路化的树dp
发现其实题给条件等价于树上独立集,直接树dp求
问题是方案怎么输出
方案的话,多记录一个$w$数组表示当前点的最小权值和(在选和不选的前提下)
转移的时候注意一下细节就好。
算完之后再跑一遍dp求一下方案。
#include <bits/stdc++.h> using namespace std; int cnt,dp[400005][3],ans,ww[400005][3],w[400005],las[400005],Arrive[400005],d[400005],nex[400005],ans1=0; void jt(int x,int y){ cnt++; nex[cnt] = las[x]; las[x] = cnt; Arrive[cnt] = y; d[x] ++; } void dfs1(int Now,int fa){ dp[Now][1] = 1; for (int i = las[Now]; i ; i = nex[i]){ int v = Arrive[i]; if (v == fa) continue; dfs1(v,Now); dp[Now][1] = dp[Now][1] + dp[v][0]; dp[Now][0] = dp[Now][0] + max(dp[v][0],dp[v][1]); ww[Now][1] = ww[Now][1]+ww[v][0]; if (dp[v][0] > dp[v][1]) ww[Now][0] = ww[Now][0] + ww[v][0]; if (dp[v][0] < dp[v][1]) ww[Now][0] = ww[Now][0] + ww[v][1]; if (dp[v][0] == dp[v][1]) ww[Now][0] = ww[Now][0]+min(ww[v][0],ww[v][1]); } ww[Now][0] += 1; ww[Now][1] += d[Now]; } void dfs2(int Now,int val,int fa){ if (val == 1){ w[Now] = d[Now]; for (int i = las[Now] ; i ; i =nex[i]){ int v = Arrive[i]; if (v == fa) continue; dfs2(v,0,Now); } }else{ w[Now] = 1; for (int i = las[Now];i;i=nex[i]){ int v = Arrive[i]; if (v == fa) continue; if (dp[v][0] > dp[v][1]) dfs2(v,0,Now); if (dp[v][0] < dp[v][1]) dfs2(v,1,Now); if (dp[v][0] == dp[v][1]){ if (ww[v][0] > ww[v][1]) dfs2(v,1,Now); else dfs2(v,0,Now); } } } ans += w[Now]; } int main(){ int N; cin>>N; for (int i = 1 ; i < N ; i ++){ int u,v; scanf("%d%d",&u,&v); jt(u,v); jt(v,u); } if (N == 2){ cout<<"2 2\n"; cout<<"1 1"; return 0; } dfs1(1,1); cout<<max(dp[1][1],dp[1][0])<<" "; if (dp[1][1] > dp[1][0]) {cout<<ww[1][1]<<endl;dfs2(1,1,1);} if (dp[1][1] < dp[1][0]) {cout<<ww[1][0]<<endl;dfs2(1,0,1);} if (dp[1][1] == dp[1][0]){ if (ww[1][0] < ww[1][1]) {cout<<ww[1][0]<<endl;dfs2(1,0,1);} else {cout<<ww[1][1]<<endl;dfs2(1,1,1);} } for (int i = 1; i <= N; i ++){ cout<<w[i]<<" "; } return 0; }
cf1646E
考虑重复数字为什么会出现
可以比较显然的发现,只有形如最左边的数字为$a^k$,在$k$不同的情况下,才会出现在$m$列里有重复
比如第$2$行和第$2^2 = 4$行就会有重复。
那么考虑我们要怎么计算不重复的。
如果我们单独的考虑一个数,比如以2为例。
$2,2^2,2^3$
它们会产生的数字是
$2^k,2^{2k},2^{3k}.....$以此类推。
那么我们就会发现,本质上这个问题就变成:
给你前$x$个自然数,会生成$k*x$,$k$从$1$取到$m$,问有多少个不重复的数字。
于是我们类似筛法的去算这个数就好了,每次暴力的循环$1~m$,由于$n$最多$10^6$,所以这个$k$最多取到$20$。
对于$n$个数字,每次统计的时候计算它的$k$是多少,然后加上预处理的答案即可。
注意别用$map$,被卡麻了
#include <bits/stdc++.h> using namespace std; int N,M; bool vis[40000005]; bool vis1[2000005]; long long dp[55]={}; int main(){ cin>>N>>M; for (int i = 1; i <= 20 ; i ++){ for (int j = i ,k=0; k < M ; k ++,j += i) if (!vis[j]){ vis[j] = true; dp[i] ++; } //cout<<"qwq"<<endl; dp[i] += dp[i-1]; } long long ans = 0; for (long long i = 2; i <= N ; i ++){ if (vis1[i]) continue; int cnt1 = 0; for (long long j = i ; j <= N ; j *= i){ if (j>N) break; vis1[j] = true; cnt1++; } //cout<<i<<" "<<cnt1<<endl; ans += dp[cnt1]; } cout<<ans+1; }
CF1562E
首先发现一个性质,就是一个点如果选了,可以先假装把它的后缀的前缀全选上,作为最初状态,这一定是一个合法状态
然后考虑转移
这个有点类似于最长上升子序列,$dp[i]$表示到$i$这个点为止的最长上升列的长度。
那么考虑$j$如何转移$i$
一定是$j$的后缀的前缀中,和$i$的后缀的前缀,从某个位置开始存在一个$i$比$j$大的情况,此时$j$可以转移到$i$
这也是$lcp$数组的用处。
实际上,我们比较两个后缀的$lcp$,然后比较它们的后一位,如果$j$比$i$小,说明$j$可以放在$i$前面,只需要把$i$和$j$的$lcp$的部分去掉即可。
于是就有了转移。
至于求$lcp$数组,因为我们这里是求两两的$lcp$,所以直接$n^2$枚举点对,递推转移即可。
$lcp[i][j] = (lcp[i+1][j+!]+1)*(s[i] == s[j])$
#include<bits/stdc++.h> using namespace std; int T; int lcp[5005][5005]; int dp[5005]; int main(){ cin>>T; while (T--){ int N; char ss[5005]; cin>>N; N--; cin >> ss; for (int i = N ; i >= 0 ; i --){ for (int j = N ; j >= 0 ; j --) lcp[i][j] = (1+lcp[i+1][j+1]) * (ss[i] == ss[j]); } int ans = 0; for (int i = 0 ; i <= N ; i ++){ dp[i] = N-i+1; for (int j = i-1; j>=0 ; j --){ if (ss[i+lcp[i][j]]>ss[j+lcp[i][j]]) dp[i] = max(dp[i],dp[j] + N - i + 1 - lcp[i][j]); } ans = max(ans,dp[i]); } cout<<ans<<'\n'; for (int i = 0 ; i <= N ; i ++) dp[i] = 0; for (int i = 0 ;i <= N; i ++) for (int j = 0 ; j <= N ; j ++){ lcp[i][j] = 0; } } return 0; }
CF1748E
没想出来(把原本序列的笛卡尔树建出来
然后会发现,其实每个点的子树表示了它作为最大值的区间。
而这时候再分析一下原题这个条件,其实就等价于新序列的树与原序列笛卡尔树相同。
$dp[i][j]$表示$i$号点最大为$j$的方案数
树形$dp$转移即可。
感觉笛卡尔树的运用都好巧妙(
#include <bits/stdc++.h> using namespace std; int T; int N,M; unordered_map<int,int> dp[300005]; int st[300005]; int a[300005],ls[300005],rs[300005]; const int fish = 1e9+7; void Clear(){ for (int i = 1; i <= N ; i ++){ dp[i].clear(); } for (int i = 0 ; i <= M ;i ++){ dp[0][i] = 1; } for (int i = 1; i <= N; i ++){ ls[i] = rs[i] = 0; } } int Build(){ int tp = 0; for (int i = 1; i <= N ; i ++){ while (tp && a[st[tp]] < a[i]) ls[i] = st[tp--]; if (tp) rs[st[tp]] = i; st[++tp] = i; } return st[1]; } void dfs(int Now){ if (ls[Now] != 0 ) dfs(ls[Now]); if (rs[Now] != 0 ) dfs(rs[Now]); for (int i = 1; i <= M ; i ++){ dp[Now][i] = (1ll*dp[ls[Now]][i-1] * dp[rs[Now]][i]%fish)%fish; (dp[Now][i] += dp[Now][i-1])%=fish; } } int main(){ cin>>T; while (T--){ scanf("%d%d",&N,&M); Clear(); for (int i = 1; i <= N ; i ++) scanf("%d",&a[i]); int rt = Build(); dfs(rt); printf("%d\n",dp[rt][M]); } return 0; }
CF1775E
链接:https://codeforces.com/contest/1775/problem/E
诈骗题(
这种某些地方$+1$,并且在某些地方$-1$的题,有点类似差分。所以我们考虑把差分数组还原回原序列
于是就是求该数组的前缀和。
于是这题就变成:每次选择一些区间,区间$+1$或者区间$-1$,问最少几次
分析到这之后思路就偏了,跑去类似于积木大赛那个题的做了。但实际上是想的太复杂了(
考虑把区间长度变为1,问题就等价于每次选一些数$+1$或一些数$-1$,最少几次让前缀和全$0$
显然分正负算一下几次就好了。
#include <bits/stdc++.h> using namespace std; long long Sum[300005]; int main(){ int T; cin>>T; while (T--){ int N; cin>>N; long long mx = 0,mn = 0; for (int i = 1 ; i <= N ; i ++){ int x; scanf("%d",&x); Sum[i] = Sum[i-1] + x; if (Sum[i] > 0) mx = max(Sum[i],mx); else mn=max(abs(Sum[i]),mn); } cout<<mx+mn<<endl; } return 0; }
CF1796D
https://codeforces.com/contest/1796
把子段和拆成两个前缀和的形式
$sum_r - sum_{l-1}$
然后显然我们如果固定了在前缀里选几个数,那当前的前缀和就固定了
于是我们只需要知道前缀的最小前缀和即可。
这个显然可以开个数组存一下
那每次无非就直接枚举当前位置用了几次去转移这个数组即可。
更新答案的时候需要后缀能够完成剩下的次数才能更新答案
注意第一个数的特判
没了(
#include <bits/stdc++.h> using namespace std; int T; long long dp[200005][25]; long long Sum[200005],a[200005]; int main(){ cin>>T; while (T--){ long long ans = 0; long long N,K,x; cin>>N>>K>>x; for (int i = 1 ; i <= N ; i ++) scanf("%lld",&a[i]); for (int i = 1 ; i <= N ; i ++) for (int j = 0 ; j <= K ; j ++) dp[i][j] = 1e9; Sum[0] = 0; for (int i = 1 ; i <= N ; i ++) Sum[i] = Sum[i-1] + a[i]; dp[1][0] = a[1] - x; dp[1][1] = a[1] + x; if (K >0 && N-1 >= K-1) ans = max(ans,a[1]+x); if (N-1 >= K)ans = max(ans,a[1]-x); for (int i = 2 ; i <= N ; i ++){ for (int j = 0 ; j <= K ; j ++){ if (j > i) break; dp[i][j] = min(dp[i-1][j],Sum[i] + j*x - (i-j) * x); if (j-1 >=0) dp[i][j] = min(dp[i][j],min(dp[i-1][j-1],Sum[i] + j*x - (i-j) * x)); if (N-i >= K-j) ans = max(ans,Sum[i] + 1ll*j*x - 1ll*(1ll*i-1ll*j) * 1ll*x-min(dp[i][j],0ll)); } } cout<<ans<<'\n'; } return 0; }

浙公网安备 33010602011771号