T1[洛谷P3941]
60分
考场想到了前缀和,二维树状数组都打出来了,应是没想出来前缀和怎么维护?我当时脑子估计是死掉了,最后明明是$O(n^4)$的思路,愣是给自己加了个$log$,考场上太蠢,我能怎么办呢
100分
我们会发现我们如果需要求一个子矩阵的和,我们不可避免的需要枚举左上角和右下角,但事实上有很多东西都是重复枚举的,有没有什么可以避免这种情况的方法呢?我们想一下,如果我们之枚举行的上下边界以及列的右边界,有没有可能可以搞出我们的子矩阵呢?我们思考一下,对于两个上下边界相同,左边界均为一的两个子矩阵,如果这两个矩阵中间的部分刚好是k的倍数,会发生什么呢?很显然,中间那一块的区间和在$\%k$的意义下为0,也就是说这两个子矩阵的区间和在$\%k$意义下是相等的,那我们可以枚举上下边界,以及右边界,开个桶,把$\%k$意义下的区间和丢进去,那么在同一个桶中的元素随便选两个,中间就是一个合法矩阵,所以答案就是$sum{C_{sum}^{2}}$,接下来我们考虑一种特殊情况,$\%k$意义下区间和为0,那事实上他们既可以两两之间包含合法区间,而他们自己也是一个合法区间所以多加一个桶中元素个数即可,当然也可以初始化$tong[0]=1$
1 #include<iostream> 2 #include<cstdio> 3 #define maxn 410 4 #define maxk 1001000 5 #define int long long 6 using namespace std; 7 int n,m,k,ans,top; 8 int tong[maxk],s[maxn]; 9 int a[maxn][maxn],qz[maxn][maxn]; 10 signed main() 11 { 12 scanf("%lld%lld%lld",&n,&m,&k); 13 for(int i=1;i<=n;++i) 14 for(int j=1;j<=m;++j) 15 { 16 scanf("%lld",&a[i][j]); a[i][j]=a[i][j]%k; 17 qz[i][j]=(((qz[i-1][j]+qz[i][j-1])%k-qz[i-1][j-1]+k)%k+a[i][j])%k; 18 } 19 for(int i=1;i<=n;++i)//上界 20 { 21 for(int j=i;j<=n;++j)//下界 22 { 23 tong[0]=1; 24 for(int p=1;p<=m;++p)//右侧 25 { 26 s[++top]=(qz[j][p]-qz[i-1][p]+k)%k; 27 tong[s[top]]++; 28 } 29 while(top) 30 { 31 ans+=tong[s[top]]*(tong[s[top]]-1)/2; 32 tong[s[top]]=0; top--; 33 } 34 } 35 } 36 printf("%lld ",ans); 37 return 0; 38 }
T2[洛谷P3942]
又是一道贪心,最近考场上就没打出来过贪心,以后考试的时候如果想不出什么来就想想贪心,还是老样子,证明贪心的正确性,对于一个叶子节点,我们考虑在他的$k$级父亲,或$k$级以下父亲放小队哪个更优?显然是$k$级父亲,因为$k$级父亲向上可覆盖的点更多,贡献更大,所以说贪心是正确的,当然了这道题dp可做,可以参照小胖守皇宫一题,是道树形dp,百度可搜
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<queue> 5 #define maxn 100100 6 using namespace std; 7 int n,k,t,js,ans; 8 int fa[maxn],head[maxn],to[maxn*2],xia[maxn*2],pd[maxn],deep[maxn]; 9 struct node{ 10 int dep,pos; 11 }; 12 bool operator < (const node &a,const node &b) 13 { 14 return a.dep<b.dep; 15 } 16 priority_queue <node> que; 17 void add(int x,int y) 18 { 19 to[++js]=y; xia[js]=head[x]; head[x]=js; 20 } 21 void dfs(int x) 22 { 23 que.push((node){deep[x],x}); 24 for(int i=head[x];i;i=xia[i]) 25 if(!fa[to[i]]) {fa[to[i]]=x; deep[to[i]]=deep[x]+1; dfs(to[i]);} 26 } 27 void Dfs(int x,int jl) 28 { 29 if(jl>k) return ; 30 pd[x]=1; 31 for(int i=head[x];i;i=xia[i]) 32 if(fa[to[i]]==x) Dfs(to[i],jl+1); 33 } 34 int main() 35 { 36 scanf("%d%d%d",&n,&k,&t); 37 for(int i=1;i<n;++i) {int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u);} 38 if(k==0) {printf("%d ",n); return 0;} 39 fa[1]=-1; deep[1]=1; dfs(1); 40 while(que.size()) 41 { 42 node ls=que.top(); que.pop(); 43 int js=0,d=ls.pos; 44 if(pd[d]==1) continue; 45 while(1) 46 { 47 if(js==k) break; 48 if(fa[d]==-1) break; 49 js++; d=fa[d]; 50 } 51 ans++; js=0; 52 while(1) 53 { 54 Dfs(d,js); 55 if(js==k) break; 56 if(fa[d]==-1) break; 57 js++; d=fa[d]; 58 } 59 } 60 printf("%d ",ans); 61 return 0; 62 }
T3[洛谷P3943]
思维含量,考察的知识面各方面都比较广
预备知识:最短路,差分,状压
首先对于这种区间修改的东西,一定要先去联想差分,可以把$O(n)$变成$O(1)$,非常好用,设$a[]$为原数组,$b[]$为差分数组,$b[i]=a[i]xora[i-1]$,对于$a$数组,我们规定点亮为0,没点亮为1,$a[0]=0$,那最后我们只需要整个差分数组都为0,也就是都与$a[0]$相等,由于差分会用到$r+1$,所以说$b$数组下标比$a$数组大一,此时对于$a$数组的区间修改就变为了对$b$数组的单点修改,也就是选一个距离满足给定距离的两个点,取反,最多多少次可以全变0,我们的目的肯定是选两个1变成0,这过程中当然会不尽人意,需要改变0,但是0只要变两次就回来了,那我们给可同时取反的点之间连边,去跑每两个1之间的最短路,即可用最少的次数把两个1变0,变零之后我们怎么知道哪两个作为一组呢?我们会发现差分数组中1最多有$2*k$个,也就是最多16个,那我们可以状压啊,所以状压一下就结束了
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #define maxk 9 6 #define maxm 70 7 #define maxn 40100 8 using namespace std; 9 int n,k,m,js,jj,top; 10 int head[maxn],to[maxn*maxm*2],xia[maxn*maxm*2],c[maxn]; 11 int a[maxn],b[maxn],mm[maxm],f[1<<(2*maxk+1)],dis[maxn],pd[maxn]; 12 int di[maxk*2][maxk*2]; 13 queue <int> s; 14 void add(int x,int y) 15 { 16 to[++jj]=y; xia[jj]=head[x]; head[x]=jj; 17 } 18 void SPFA(int x) 19 { 20 memset(dis,0x3f,sizeof(dis)); 21 dis[x]=0; pd[x]=1; top=0; s.push(x); 22 while(s.size()) 23 { 24 int ls=s.front(); s.pop(); 25 for(int i=head[ls];i;i=xia[i]) 26 { 27 int lss=to[i]; 28 if(dis[lss]>dis[ls]+1) 29 { 30 dis[lss]=dis[ls]+1; 31 if(!pd[lss]) {s.push(lss); pd[lss]=1;} 32 } 33 } 34 pd[ls]=0; 35 } 36 } 37 int main() 38 { 39 scanf("%d%d%d",&n,&k,&m); 40 memset(f,0x3f,sizeof(f)); memset(di,0x3f,sizeof(di)); f[0]=0; 41 for(int i=1;i<=k;++i) {int x; scanf("%d",&x); a[x]=1;} 42 for(int i=1;i<=m;++i) scanf("%d",&mm[i]); 43 for(int i=1;i<=n+1;++i) 44 { 45 b[i]=a[i]^a[i-1]; 46 if(b[i]==1) c[++js]=i; 47 } 48 /*for(int i=1;i<=n;++i) 49 for(int j=1;j<=m;++j) 50 { 51 if(i+mm[j]>n+1) continue; 52 add(i,i+mm[j]); add(i+mm[j],i); 53 }*/ 54 for(int i=1;i<=n+1;++i) 55 { 56 for(int j=1;j<=m;++j) 57 { 58 if(i-mm[j]>=0) add(i,i-mm[j]); 59 if(i+mm[j]<=n+1) add(i,i+mm[j]); 60 } 61 } 62 for(int i=1;i<=js;++i) 63 { 64 SPFA(c[i]); 65 for(int j=i+1;j<=js;++j) {di[i-1][j-1]=dis[c[j]]; di[j-1][i-1]=dis[c[j]];} 66 } 67 for(int i=0;i<(1<<js);++i) 68 { 69 int qd=-1,j=0; 70 while(j<js) 71 { 72 if(!(i&(1<<j))) 73 { 74 if(qd==-1) qd=j; 75 else f[i|(1<<qd)|(1<<j)]=min(f[i|(1<<qd)|(1<<j)],f[i]+di[qd][j]); 76 } 77 j++; 78 } 79 } 80 printf("%d ",f[(1<<js)-1]); 81 return 0; 82 }
如果想快一点的话,说不定可以试试堆优化$dijkstra$