2019.10.22 NOIP模拟测试 day2
N的范围很小,可以用n^3的方法解决。但是我一开始想的是线段树维护矩阵的和,但是复杂度一直降不下来,还多一个log,最后还没有n^4的暴力高。
N^4暴力(70分):就是二维前缀和,n^4枚举即可。
100分做法:
现预处理出二维前缀和,然后枚举行和列,枚举列可以直接变成少掉一维枚举,就是看是否能被整除,然后开一个桶统计一下就可以了,复杂度n^3。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=505; const int nn=1e6+7; int a[maxn][maxn]; long long sum[maxn][maxn]; int n,m,k; long long ans; int cnt[nn]; int b[nn]; int main(){ freopen("rally.in","r",stdin); freopen("rally.out","w",stdout); scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; } for(int i=0;i<n;i++){ for(int j=i+1;j<=n;j++){ cnt[0]=1; for(int o=1;o<=m;o++){ b[o]=(sum[j][o]-sum[i][o]+k)%k; ans+=cnt[b[o]]; cnt[b[o]]++; } for(int o=1;o<=m;o++) cnt[b[o]]=0; } } printf("%lld\n",ans); return 0; }
一看,这不是最小点覆盖吗,直接上树形dp最小点覆盖,结果最后wa的很惨。
正解:树形dp(那是肯定的),只不过dp方程极其复杂,考虑有没有简单一点的方法,当然就是贪心。贪心的枚举每个点最多能覆盖掉的点的个数打标记,用一个堆来存,从深度深的点向它的第k祖先上打标记。看哪些点没被覆盖即可。如果没被覆盖,就增加一个驿站。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; struct node{ int nxt,to; }edge[maxn*2]; int head[maxn],cnt; int deg[maxn]; bool vis[maxn]; void add(int x,int y){ edge[++cnt].nxt=head[x]; edge[cnt].to=y; head[x]=cnt; } priority_queue<pair<int,int> >q; int fa[maxn]; int dep[maxn]; int tot; int n,m,k,x,y,t; void dfs1(int x){ for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].to; if(!dep[v]){ dep[v]=dep[x]+1; fa[v]=x; dfs1(v); } } } void mark(int x,int f,int dep){ vis[x]=true; if(dep==k) return; for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].to; if(v==f) continue; mark(v,x,dep+1); } } int getfa(int x){ int num=1; while(num<=k){ x=fa[x]; num++; } return x; } int main(){ freopen("general.in","r",stdin); freopen("general.out","w",stdout); scanf("%d%d%d",&n,&k,&t); for(int i=1;i<n;i++){ scanf("%d%d",&x,&y); add(x,y);add(y,x); } fa[1]=1;dep[1]=1;dfs1(1); for(int i=1;i<=n;i++) q.push(make_pair(dep[i],i)); while(!q.empty()){ int x=q.top().second; q.pop(); if(vis[x]) continue; tot++; int kk=getfa(x); mark(kk,kk,0); } printf("%lld\n",tot); return 0; }
输出0,1,2,3运气最好的是输出2,有12分。
看k的范围很小,就想是否可以状压处理,对于每次区间异或,可以将其转化为差分的形式,维护一个差分数组,然后看差分数组中每个不为0的点的位置所能到达的其他区间,预处理出他们之间的距离,bfs即可。然后就是状压dp。设dp[i]表示状态为i时的最小步数,我们的最终状态是灯全亮,就这样dp下去求解即可。转移方程也非常简单。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; const int N=550; int dis[20][maxn]; int a[maxn],b[maxn]; int cha[maxn]; int n,k,m; int cnt[maxn],num; int x; bool vis[maxn]; long long dp[1<<20|1]; int state[N][N]; void bfs(int x,int *dis){ queue<int> q; memset(vis,false,sizeof(vis)); q.push(x); dis[x]=0; vis[x]=1; while(!q.empty()){ int u=q.front(); q.pop(); for(int i=1;i<=m;i++){ int y=u-b[i]; if(y>0&&!vis[y]){ dis[y]=dis[u]+1; q.push(y); vis[y]=true; } y=u+b[i]; if(y<=n+1&&!vis[y]){ dis[y]=dis[u]+1; q.push(y); vis[y]=true; } } } } int main(){ freopen("starlit.in","r",stdin); freopen("starlit.out","w",stdout); scanf("%d%d%d",&n,&k,&m); memset(a,1,sizeof(a)); for(int i=1;i<=k;i++){ scanf("%d",&x); a[x]=0; } for(int i=1;i<=m;i++) scanf("%d",&b[i]); for(int i=1;i<=n+1;i++) cha[i]=a[i]^a[i-1]; memset(dis,88,sizeof(dis)); for(int i=1;i<=n+1;i++){ if(cha[i]){ cnt[++num]=i; bfs(i,dis[num]); } } memset(dp,88,sizeof(dp)); for(int i=1;i<=num;i++){ for(int j=1;j<=num;j++){ state[i][j]=(1<<i-1)|(1<<j-1); } } dp[0]=0; int maxx=1<<num; for(int i=0;i<maxx;i++){ for(int j=1;j<=num;j++){ if((i&(1<<j-1))==0){ for(int k=j+1;k<=num;k++){ if((i&(1<<k-1))==0){ dp[i|(state[j][k])]=min(dp[i|state[j][k]],dp[i]+dis[j][cnt[k]]); } } } } } printf("%lld\n",dp[maxx-1]); return 0; }