T1:入阵曲
题目大意:给定一个N*M的矩形,问一共有多少个子矩形,使得矩形内所有书的和为k的倍数。
60%:N,M<=80
枚举矩形的左上角和右下角,用二维前缀和求出数字之和。
时间复杂度$O(N^4)$
100%
我们发现美剧每个矩形的复杂度已经为N4,那么我们改为枚举矩形的边界。先枚举左右边界,再从上到下枚举下边界,两个矩形相减便可得出所有矩形,枚举时将每个矩形的区间和压入桶中,每次枚举时直接在桶中查询,左右边界更换时清桶。注意下边界要从0开始枚举。
时间复杂度$O(N^3)$
记得要卡常。
Code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<cstdio> 3 const int L=1<<20|1; 4 char buffer[L],*S,*T; 5 #define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++) 6 #define LL unsigned long long 7 #define rint register unsigned int 8 using namespace std; 9 const unsigned int N=410; 10 int n,m,d; 11 int s[N][N]; 12 unsigned int dfn[2000010],t[2000010]; 13 inline int read() 14 { 15 rint s=0;char c=getchar(); 16 while(c<48||c>57) c=getchar(); 17 while(c>=48&&c<=57)s=(s<<3)+(s<<1)+(c^48),c=getchar(); 18 return s; 19 } 20 int main() 21 { 22 n=read();m=read();d=read(); 23 for(rint i=1;i<=n;++i){ 24 for(rint j=1;j<=m;++j) 25 s[i][j]=(s[i-1][j]+s[i][j-1]-s[i-1][j-1]+read())%d; 26 } 27 LL ans=0;rint df=0; 28 for(rint i=1;i<=m;++i){ 29 for(rint j=i;j<=m;++j){ 30 dfn[0]=++df;t[0]=1; 31 for(rint k=1;k<=n;++k){ 32 register int now=((s[k][j]-s[k][j-i])%d+d)%d; 33 if(dfn[now]!=df){ 34 dfn[now]=df;t[now]=1; 35 } 36 else{ 37 ans+=t[now];++t[now]; 38 } 39 } 40 } 41 } 42 printf("%lld\n",ans); 43 return 0; 44 }
T2:将军令:
题目大意:给定一棵树,和一个距离K,在某个节点驻扎军队可以占领所有与该节点距离不超过K的点,求最少要驻扎多少军队。
此题和小胖守皇宫类似,不过权值均为一,可以用贪心做。
将所有的点按深度排序,从前往后扫,遇到未标记的点,则从他的K辈祖先处开始进行dfs,暴力标记,将ans加一,扫到最后的结果即是答案。
贪心策略证明:
由于每次找的是深度最大的点,那么每次dfs一定能标记到当前子树的所有儿子,同时要想使其他子树需要的军队尽可能少,则应再次条件下尽可能向上,越向上越能标记更多的点。证明完毕,贪心策略正确。
时间复杂度$O(NK)$
Code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int N=100010; 6 int n,k,t,m=0,ans=0; 7 int fi[N],d[N],f[N]; 8 bool v[N]; 9 struct edge{ 10 int v,ne; 11 }e[N<<1]; 12 struct point{ 13 int d,id; 14 }p[N]; 15 void add(int x,int y) 16 { 17 e[++m].v=y; 18 e[m].ne=fi[x];fi[x]=m; 19 } 20 bool comp(const point a1,const point a2) 21 { 22 return a1.d>a2.d; 23 } 24 int read() 25 { 26 int s=0;char c=getchar(); 27 while(c<'0'||c>'9') c=getchar(); 28 while(c>='0'&&c<='9'){ 29 s=(s<<3)+(s<<1)+c-'0'; 30 c=getchar(); 31 } 32 return s; 33 } 34 void dfs(int x,int pa) 35 { 36 p[x].id=x; 37 for(int i=fi[x];i!=0;i=e[i].ne){ 38 int y=e[i].v; 39 if(y==pa) continue; 40 f[y]=x; 41 p[y].d=p[x].d+1; 42 dfs(y,x); 43 } 44 } 45 void work(int x,int pa,int d) 46 { 47 v[x]=true; 48 if(d==0) return; 49 for(int i=fi[x];i!=0;i=e[i].ne){ 50 int y=e[i].v; 51 if(y==pa) continue; 52 work(y,x,d-1); 53 } 54 } 55 int main() 56 { 57 n=read();k=read();t=read(); 58 for(int i=1;i<n;i++){ 59 int x=read(),y=read(); 60 add(x,y);add(y,x); 61 } 62 p[1].d=0;dfs(1,0); 63 sort(p+1,p+n+1,comp); 64 for(int i=1;i<=n;i++){ 65 int x=p[i].id; 66 if(!v[x]){ 67 for(int i=1;i<=k&&f[x]!=0;i++) 68 x=f[x]; 69 work(x,0,k);ans++; 70 } 71 } 72 printf("%d\n",ans); 73 return 0; 74 }
T3:星空
题目大意:给一个0/1串,每次能将一个给定长度的区间去反,求最少要几次将整个串变为零。
0/1串长度:N<=40000,区间长度数:M<=64,1的个数:K<=8
24%做法:N<=16
直接状压,跑BFS即可。DFS会T飞
复杂度$O(N*2^N)$
100%做法:
我们发现相对于N,K很小,可以用来做做文章。
开始转化题意:
我们设灯亮为0,灯灭为1,则序列全为0时,所有灯都点亮。
不难发现当N很大时,该序列中仅有K个1,我们从0位开始将每一位与其后一位亦或,则最多有2K个数与前一位不同,最多有2K个1,每次选择一段区间[L,R]取反,仅会改变第L-1位和第R位的值,相当于将序列中一定距离的点同时取反。
题意转化为:给一个0/1串,每次选择相距给定长度的点同时取反,最少几次能将整个串全变为0。
若选择的两个书均为0,没有任何意义;
若选择的两个数均为1,则相互抵消;
若其中一个数为0,另一个数为1,相当与将1移动一定距离。
题意再次转化为:数轴上有最多2K个点,每次将一个点移动一段距离,两个点碰在一起后同时消失,最少几次能让所有点消失。
此时K的范围状压可以接受,BFS算出两点间移动的最少次数,用状压DP简单解决。
时间复杂度$O(MNK+K*2^K)$或$O(MNK+K^2*2^K)$
Code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 using namespace std; 6 const int N=40010; 7 const int inf=99999999; 8 int n,k,m,tot=0; 9 int a[N],b[N],v[N],c[20],d[20][20],e[100]; 10 int dp[1<<16]; 11 queue<int> q; 12 int read() 13 { 14 int s=0;char c=getchar(); 15 while(c<'0'||c>'9') c=getchar(); 16 while(c>='0'&&c<='9'){ 17 s=(s<<3)+(s<<1)+c-'0'; 18 c=getchar(); 19 } 20 return s; 21 } 22 void bfs(int pos) 23 { 24 int now=c[pos]; 25 memset(v,0,sizeof(v)); 26 v[now]=1;q.push(now); 27 while(!q.empty()){ 28 int x=q.front();q.pop(); 29 for(int i=1;i<=m;i++){ 30 int y=x+e[i]; 31 if(y<=n+1&&v[y]==0){ 32 v[y]=v[x]+1;q.push(y); 33 } 34 y=x-e[i]; 35 if(y>=1&&v[y]==0){ 36 v[y]=v[x]+1;q.push(y); 37 } 38 } 39 } 40 for(int i=1;i<=tot;i++){ 41 if(i!=pos){ 42 if(v[c[i]]==0) d[pos][i]=inf; 43 else d[pos][i]=v[c[i]]-1; 44 } 45 } 46 } 47 int main() 48 { 49 n=read();k=read();m=read(); 50 for(int i=1;i<=k;i++){ 51 int x=read(); 52 a[x]=1; 53 } 54 for(int i=0;i<=n;i++){ 55 b[i]=a[i]^a[i+1]; 56 if(b[i]==1) c[++tot]=i+1; 57 } 58 for(int i=1;i<=m;i++) 59 e[i]=read(); 60 for(int i=1;i<=tot;i++) 61 bfs(i); 62 for(int i=1;i<(1<<tot);i++) 63 dp[i]=inf; 64 for(int i=0;i<(1<<tot);i++){ 65 for(int j=1;j<=tot;j++){ 66 if(((i>>(j-1))&1)==1) continue; 67 for(int g=1;g<=tot;g++){ 68 if(g==j) continue; 69 if(((i>>(g-1))&1)==1) continue; 70 dp[i|(1<<(j-1))|(1<<(g-1))]=min(dp[i|(1<<(j-1))|(1<<(g-1))],dp[i]+d[j][g]); 71 } 72 } 73 } 74 printf("%d\n",dp[(1<<tot)-1]); 75 return 0; 76 }