Noip模拟38 2021.8.13
T1 a
跟入阵曲很像,但是忘记入阵曲这题的思路是什么了
这里再提一下,入阵曲是子矩阵和是$k$的倍数,这道题目是子矩阵和是在一段区间内$[L,R]$
因为这道题$n$特别小,$m$较大,考虑复杂度为$O(n^2m)$的做法
那么按照入阵曲的思想,枚举行的上下边界,每次处理出这两行之间的前缀和,记为$sm_k$
然后使用双指针, $l,r$分别维护的是横向框出的这一段前缀和的合法的最左端点和最右端点
只要每次枚举找到这两个量,直接把$r-l+1$累加到$ans$里面就可以完成统记
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 inline int read(){ 5 int x=0,f=1; char ch=getchar(); 6 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 7 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 8 return x*f; 9 } 10 inline void write(register int x){ 11 char ch[20]; int len=0; 12 if(x<0) x=~x+1, putchar('-'); 13 do{ ch[len++]=x%10+(1<<5)+(1<<4); x/=10; }while(x); 14 for(register int i=len-1;i>=0;--i) putchar(ch[i]); putchar('\n'); 15 } 16 const int NN=5e4+5; 17 char s[NN]; 18 int n,m,L,R,sum[35][NN],ans,sm[NN],l,r,tmp1,tmp0; 19 inline void spj4(){ 20 for(register int i=1;i<=n;i++) for(register int j=1;j<=m;j++) if(L<=i*j&&i*j<=R) ans+=(n-i+1)*(m-j+1); 21 write(ans); 22 } 23 inline void spj2(){ 24 for(register int x1=1;x1<=n;x1++) for(register int y1=1;y1<=m;y1++) ans+=(n-x1+1)*(m-y1+1); 25 write(ans); 26 } 27 namespace WSN{ 28 inline short main(){ 29 n=read(); m=read(); 30 for(register int i=1;i<=n;++i){ 31 scanf("%s",s+1); 32 for(register int j=1;j<=m;++j){ 33 bool oo=s[j]-'0'; 34 oo?(++tmp1):(++tmp0); 35 sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+oo; 36 } 37 } 38 L=read(); R=read(); 39 if(L<=0&&R>=n*m){spj2();return 0;} 40 if(tmp1==n*m){spj4();return 0;} 41 for(register int i=0;i<n;++i) for(register int j=i+1;j<=n;++j){ 42 for(register int k=1;k<=m;++k) sm[k]=sum[j][k]-sum[i][k]; 43 for(register int k=1,l=1,r=1;k<=m;++k) if(sm[k]>=L){ 44 while(sm[k]-sm[l-1]>R&&l<k) ++l; 45 while(sm[k]-sm[r]>=L&&r<k) ++r; 46 ans+=r-l+1; 47 } 48 } 49 write(ans); 50 return 0; 51 } 52 } 53 signed main(){return WSN::main();}
提示一下
以后能不用$continue$就别用了,要不时间会爆炸,原本用的这个判断$sm_k>=L$,结果直接$T$飞,调了好久。。。
T2 b
需要大量题意转化才能把它变得可做
题目问所有方案$gcd$的和,我们不妨把它换成$i \in [1,1e5]$,问有多少种方案使得$gcd=i$
这样还是不太可做,再转化一下
我们可以求在$i \in [1,1e5]$中,满足$i|gcd$的总方案数,最后用容斥删去重叠部分就可得到答案。
我们考虑转化后的问题。
可以用一个$vector_{i,j}$存值为$i$的数在哪几行出现过
然后对值域内的所有$i$进行约数分解,将找到的$j$存入一个数组$cnt_{i,j}$表示第$i$行内$j$的倍数有几个
将这些预处理完之后,可以开始统计答案
记录一个$sum_i$数组表示所选的数均是$i$的倍数的方案数
那么对于每一个有$cnt$的$i$,其总方案数为$\prod_{j=1}^{n}(cnt_{j,i}+1)-1$,每次乘上$cnt_{j,i}+1$表示这一层选还是不选,最后减去一种都不选的情况
这样可以求出数组$sum$
那么再考虑重叠,直接枚举每一个$i$的倍数然后减去它的$sum_t$就可以得到$gcd=i$的方案数
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 inline int read(){ 5 int x=0,f=1; char ch=getchar(); 6 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 7 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 8 return x*f; 9 } 10 inline void write(int x){ 11 char ch[20]; int len=0; 12 if(x<0) x=~x+1, putchar('-'); 13 do{ ch[len++]=x%10+(1<<5)+(1<<4); x/=10; }while(x); 14 for(int i=len-1;i>=0;--i) putchar(ch[i]); putchar('\n'); 15 } 16 const int p=1e9+7,NN=1e5+5; 17 int n,m,a[25][NN],ans,cnt[25][NN],sum[NN]; 18 vector<int> hg[NN]; 19 namespace WSN{ 20 inline short main(){ 21 n=read(); m=read(); 22 for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) 23 a[i][j]=read(),hg[a[i][j]].push_back(i); 24 for(int i=1e5;i;--i) if(hg[i].size()){ 25 int sz=hg[i].size(),ii=sqrt(i); 26 for(int j=1;j<=ii;++j) if(i%j==0) 27 for(int k=0;k<sz;++k){ 28 ++cnt[hg[i][k]][j]; 29 if(j*j!=i) ++cnt[hg[i][k]][i/j]; 30 } 31 } 32 for(int i=1e5;i;--i){ 33 sum[i]=1; 34 for(int j=1;j<=n;++j) 35 sum[i]=sum[i]*(cnt[j][i]+1)%p; 36 --sum[i]; 37 } 38 for(int i=1e5;i;i--){ 39 int t=i<<1; 40 while(t<=1e5){sum[i]=(sum[i]-sum[t]+p)%p;t+=i;} 41 ans=(ans+sum[i]*i%p)%p; 42 }write(ans); 43 return 0; 44 } 45 } 46 signed main(){return WSN::main();}
T3 c
考场上开错的神仙题,点分治根本还未学习,于是下午抽出将近一个半小时学习了点分治。
然而这道题是点分树上$dp$,更加恶心,不过$dp$想明白还算比较好理解。
先转化题意,我们发现他说的无向图还有环,其实只是在一棵树上多链接了几条重边,就只是每条边可能有多种颜色,
其他别的都是唬人的
我们最暴力的做法就是树剖找到$LCA$,然后暴力跳爹并统计颜色最优值
这里有一个$25$分暴力,可以适当的启发正解
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 inline int read(){ 5 int x=0,f=1; char ch=getchar(); 6 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 7 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 8 return x*f; 9 } 10 inline void write(int x){ 11 char ch[20]; int len=0; 12 if(x<0) x=~x+1, putchar('-'); 13 do{ ch[len++]=x%10+(1<<5)+(1<<4); x/=10; }while(x); 14 for(int i=len-1;i>=0;--i) putchar(ch[i]); putchar('\n'); 15 } 16 const int NN=3e5+5; 17 int n,m,q; 18 int fa[NN],son[NN],siz[NN],dep[NN],top[NN],dfn[NN],rk[NN],cnt; 19 vector<int> p[NN]; 20 struct SNOW{ 21 int fr,to,next; 22 vector<int> c; 23 };SNOW e[NN<<1]; int head[NN],rp; 24 inline void add(int x,int y,int z){ 25 for(int i=head[x];i;i=e[i].next) 26 if(y==e[i].to){ 27 for(auto co:e[i].c) if(co==z) return; 28 e[i].c.push_back(z);return; 29 } 30 e[++rp].to=y; e[rp].next=head[x]; head[x]=rp; e[rp].fr=x; e[rp].c.push_back(z); 31 } 32 inline void dfs1(int f,int x){ 33 fa[x]=f; siz[x]=1; dep[x]=dep[f]+1; 34 for(int i=head[x];i;i=e[i].next){ 35 int y=e[i].to; if(y==f) continue; 36 p[y]=e[i].c; 37 dfs1(x,y); siz[x]+=siz[y]; 38 if(siz[son[x]]<siz[y]) son[x]=y; 39 } 40 } 41 inline void dfs2(int x,int t){ 42 dfn[x]=++cnt; rk[cnt]=x; top[x]=t; 43 if(son[x]) dfs2(son[x],t); 44 for(int i=head[x];i;i=e[i].next){ 45 int y=e[i].to; 46 if(y!=fa[x]&&y!=son[x]) dfs2(y,y); 47 } 48 } 49 inline int LCA(int x,int y){ 50 while(top[x]!=top[y]){ 51 if(dep[top[x]]<dep[top[y]]) swap(x,y); 52 x=fa[top[x]]; 53 }if(dfn[x]>dfn[y]) swap(x,y); 54 return x; 55 } 56 int cnt1,cnt2; 57 struct node{ 58 int num,fin; 59 };node ans1[NN],ans2[NN]; 60 vector<int> pre; 61 inline void dfs(int x,int ed,int opt){ 62 // cout<<x<<endl; 63 if(x==ed){ 64 if(pre.size()==0) return; 65 int tmp=1,fir=pre[0],siz=pre.size(); 66 for(int i=0;i<siz;i++){ 67 if(fir!=pre[i]) ++tmp,fir=pre[i]; 68 } 69 if(!opt) ans1[++cnt1]=(node){tmp,pre[siz-1]}; 70 else ans2[++cnt2]=(node){tmp,pre[siz-1]}; 71 // if(opt){for(int i=0;i<siz;i++) cout<<pre[i]<<" ";cout<<endl;} 72 return; 73 } 74 int f=fa[x]; 75 if(p[x].size()==1){ 76 pre.push_back(p[x][0]); 77 dfs(f,ed,opt); 78 pre.pop_back(); 79 } 80 else{ 81 for(int i=0;i<p[x].size();i++){ 82 int co=p[x][i]; 83 // cout<<"co="<<co<<endl; 84 pre.push_back(co); 85 dfs(f,ed,opt); 86 pre.pop_back(); 87 } 88 } 89 } 90 namespace WSN{ 91 inline short main(){ 92 n=read();m=read(); 93 for(int i=1;i<=m;i++){ 94 int u=read(),v=read(),w=read(); 95 if(u>v) swap(u,v); add(u,v,w); add(v,u,w); 96 }dfs1(0,1); dfs2(1,1); 97 q=read(); 98 /* for(int i=1;i<=n;i++){ 99 cout<<"point="<<i<<" "; 100 for(int j=0;j<p[i].size();j++){ 101 cout<<p[i][j]<<" "; 102 }cout<<endl; 103 }*/ 104 /* for(int i=1;i<=rp;i++){ 105 cout<<e[i].fr<<" "<<e[i].to<<" "; 106 for(int j=0;j<e[i].c.size();j++){ 107 cout<<e[i].c[j]<<" "; 108 } 109 cout<<endl; 110 }*/ 111 while(q--){ 112 int x=read(),y=read(),lca=LCA(x,y); 113 int wsn=0; 114 cnt1=cnt2=0; pre.clear(); 115 if(lca==x){ 116 dfs(y,lca,1); 117 for(int i=1;i<=cnt2;i++) wsn=max(wsn,ans2[i].num); 118 write(wsn);continue; 119 } 120 if(lca==y){ 121 dfs(x,lca,0); 122 for(int i=1;i<=cnt1;i++) wsn=max(wsn,ans1[i].num); 123 write(wsn);continue; 124 } 125 // cout<<fa[x]<<" "<<fa[y]<<endl; 126 dfs(x,lca,0); 127 pre.clear(); 128 dfs(y,lca,1); 129 /* for(int i=1;i<=cnt1;i++) cout<<ans1[i].num<<" ";cout<<endl; 130 for(int i=1;i<=cnt2;i++) cout<<ans2[i].num<<" ";cout<<endl;*/ 131 for(int i=1;i<=cnt1;i++){ 132 for(int j=1;j<=cnt2;j++){ 133 node s1=ans1[i],s2=ans2[j]; 134 // cout<<s1.num<<" "<<s2.num<<endl; 135 wsn=max(wsn,s1.num+s2.num-(s1.fin==s2.fin)); 136 } 137 } 138 write(wsn); 139 } 140 return 0; 141 } 142 } 143 signed main(){return WSN::main();}
好,现在我们考虑如何在合理的时间内过掉此题。
我们可以发现,每条边得到最优策略是不必保留他给出的所有重边的。
经过考虑,可以发现每一条边最多只留下$3$条不一样颜色的重边即可得到最优策略
那么我们可以先将所有边存到一个$vector$里面,然后进行预处理边。
1 vector<pii > e[NN]; 2 vector<pair<int,vii > > g[NN];//vii define了一下 3 inline void dfs(int fa,int x){ 4 sort(e[x].begin(),e[x].end()); 5 e[x].erase(unique(e[x].begin(),e[x].end()),e[x].end());//排序之后去掉重复的边 6 for(int i=0,j,y;i<e[x].size();i=j){ 7 y=e[x][i].fi;j=i+1; 8 while(j<e[x].size()&&y==e[x][j].fi) ++j; 9 if(fa==y) continue; 10 g[x].pb(mp(y,vii())); g[y].pb(mp(x,vii())); 11 for(int k=i;k<j&&k<i+3;k++){ 12 g[x].back().se.pb(e[x][k].se); 13 g[y].back().se.pb(e[x][k].se);//建树 14 }dfs(x,y); 15 } 16 }
然后按照点分树的建立方案将点分树搞出来,同时记录一个倍增的祖先数组,准备找$LCA$
因为我们要将每组询问的次序,$u$,$v$,存到一个$vector$里面,他的第一维是$LCA$
这样的话方便以后的$dp$,因为$LCA$,可以当作一个重心在分治中起转折点的作用
然后记录一个辅助转移的数组$son_x$表示在分治中心到$x$的一条路经上,重心的儿子。
之后考虑$dp$,$dp_{i,j,k}$表示分治中心(也就是每次找到的重心)到$i$点的路径上,
重心到其儿子的边的颜色为$j$,$i$点上面的边为$k$的最优方案
$dp$的时候枚举这两个边的所有可能情况,即可进行转移,因为除了这两条边可能有限制以外
其他的边都可以不重复
然后特判一下$u==v$和$u$或者$v$中有一个是$lca$的情况,即可出解。
#include<bits/stdc++.h> #define pb push_back #define mp make_pair #define fi first #define se second #define pii pair<int,int> #define vii vector<int> using namespace std; const int NN=5e5+5,inf=0x3fffffff; int n,m,q; vector<pii > e[NN]; vector<pair<int,vii > > g[NN]; inline void dfs(int fa,int x){ sort(e[x].begin(),e[x].end()); e[x].erase(unique(e[x].begin(),e[x].end()),e[x].end()); for(int i=0,j,y;i<e[x].size();i=j){ y=e[x][i].fi;j=i+1; while(j<e[x].size()&&y==e[x][j].fi) ++j; if(fa==y) continue; g[x].pb(mp(y,vii())); g[y].pb(mp(x,vii())); for(int k=i;k<j&&k<i+3;k++){ g[x].back().se.pb(e[x][k].se); g[y].back().se.pb(e[x][k].se); }dfs(x,y); } } struct SNOW{int id,x,y;};vector<SNOW> b[NN]; struct snow{ int S,wc,siz[NN],dep[NN],wt[NN]; int fa[20][NN],dp[NN][3][3],son[NN],ans[NN]; bool st[NN]; inline void get_wc(int f,int x){ siz[x]=1; wt[x]=0; for(int i=0,y;i<g[x].size();i++) if(!st[y=g[x][i].fi]&&y!=f){ get_wc(x,y); siz[x]+=siz[y]; wt[x]=max(wt[x],siz[y]); } wt[x]=max(wt[x],S-siz[x]); if(wt[x]<wt[wc]) wc=x; } inline void pre(int f,int x){ st[x]=1; dep[x]=dep[f]+1; fa[0][x]=f; for(int i=1;i<20;i++) fa[i][x]=fa[i-1][fa[i-1][x]]; for(int i=0,y;i<g[x].size();i++) if(!st[y=g[x][i].fi]){ S=siz[y]; wt[wc=0]=inf; get_wc(0,y); pre(x,wc); } } inline int LCA(int x,int y){ if(dep[x]>dep[y]) swap(x,y); for(int i=19;i>=0;i--) if(dep[x]<=dep[y]-(1<<i)) y=fa[i][y]; if(x==y) return x; for(int i=19;i>=0;i--) if(fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y]; return fa[0][x]; } vii col[NN]; inline void calc(int f,int x,int rtson){ son[x]=rtson; for(int i=0,y;i<g[x].size();i++) if(!st[y=g[x][i].fi]&&y!=f){ col[y]=g[x][i].se; for(int j=0;j<col[rtson].size();j++) for(int k=0;k<col[y].size();k++){ dp[y][j][k]=-inf; for(int l=0;l<col[x].size();l++) dp[y][j][k]=max(dp[y][j][k],dp[x][j][l]+(col[y][k]!=col[x][l])); } calc(x,y,rtson); } } inline void solve(int x){ st[x]=1; for(int i=0,y;i<g[x].size();i++) if(!st[y=g[x][i].fi]){ col[y]=g[x][i].se; for(int j=0;j<col[y].size();j++) for(int k=0;k<col[y].size();k++) dp[y][j][k]=j==k?1:-inf; calc(x,y,y); } for(int i=0;i<b[x].size();i++){ int u=b[x][i].x,v=b[x][i].y; if(v==x) swap(u,v); if(u==v) ans[b[x][i].id]=0; else if(u==x){ for(int j=0;j<col[son[v]].size();j++) for(int k=0;k<col[v].size();k++) ans[b[x][i].id]=max(ans[b[x][i].id],dp[v][j][k]); } else{ for(int j=0;j<col[son[u]].size();j++) for(int k=0;k<col[son[v]].size();k++) for(int l=0;l<col[u].size();l++) for(int r=0;r<col[v].size();r++) ans[b[x][i].id]=max(ans[b[x][i].id],dp[u][j][l]+dp[v][k][r]-(col[son[u]][j]==col[son[v]][k])); } } for(int i=0,y;i<g[x].size();i++) if(!st[y=g[x][i].fi]){ S=siz[y]; wt[wc=0]=inf; get_wc(0,y); solve(wc); } } inline void work(){ S=wt[wc=0]=n; get_wc(0,1); pre(0,wc); // for(int i=1;i<=n;i++) cout<<fa[0][i]<<endl; for(int i=1,x,y;i<=q;i++){ scanf("%d%d",&x,&y); b[LCA(x,y)].pb((SNOW){i,x,y}); // cout<<LCA(x,y)<<endl; }memset(st,0,sizeof(st)); S=wt[wc=0]=n; get_wc(0,1); solve(wc); for(int i=1;i<=q;i++) printf("%d\n",ans[i]); } }wsn; namespace WSN{ inline short main(){ scanf("%d%d",&n,&m); for(int i=1,u,v,w;i<=m;i++){ scanf("%d%d%d",&u,&v,&w); e[u].pb(mp(v,w)); e[v].pb(mp(u,w)); } scanf("%d",&q); if(!q) return 0; dfs(0,1); /* for(int i=1;i<=n;i++){ for(int j=0;j<g[i].size();j++){ int pos=g[i][j].fi,num=g[i][j].se.size(); cout<<i<<" "<<pos<<" ||"; for(int k=0;k<num;k++){ cout<<g[i][j].se[k]<<" "; } cout<<endl; } }*/ wsn.work(); return 0; } } signed main(){return WSN::main();}