T1:入阵曲
n,m<=400,k<=1e6
分析:
考虑只有一行的情况:
将这一行求前缀和后,一段区间的和x=sum[r]-sum[l-1],如果x%k==0,那么sum[r]%k - sum[l-1]%k == 0
转化一下,也就是说:sum[r]与sum[l-1]在模k的意义下相等。
所以对于一行的来说,O(n)地for一遍,用一个桶记录一下模k意义下的值相同的个数,ans+=cnt[k]*(cnt[k]-1)/2
对于多行的来说,枚举上界和下界,再将这一块子矩阵对列求前缀和压缩成一列,转化成上面的问题。
复杂度:O(n^3)
#include<bits/stdc++.h> using namespace std; #define N 405 #define M 1000005 #define ll long long #define ri register int int n,m,kk,ans=0,t[M],q[M]; ll a[N][N],l[N][N]; int main() { freopen("rally.in","r",stdin); freopen("rally.out","w",stdout); scanf("%d%d%d",&n,&m,&kk); for(ri i=1;i<=n;++i) for(ri j=1;j<=m;++j) scanf("%lld",&a[i][j]); for(ri j=1;j<=m;++j) for(ri i=1;i<=n;++i) l[i][j]=l[i-1][j]+a[i][j];//对列求前缀和 ll ans=0; for(ri i=1;i<=n;++i) for(ri j=i;j<=n;++j){ //memset(t,0,sizeof(t));memset会超时 ll sum=(l[j][1]-l[i-1][1]) %kk,top=0;//将一列压缩成一行,对行求前缀和 for(ri k=1;k<=m;++k){ if(!t[sum]) q[++top]=sum; t[sum] ++;//桶记录 sum=(sum+l[j][k+1]-l[i-1][k+1]) %kk; } ans+=t[0]; for(ri k=1;k<=top;++k) if(t[q[k]]) ans+=(t[q[k]]-1)*t[q[k]]/2,t[q[k]]=0;//清空 } printf("%lld\n",ans); return 0; } /* 2 3 2 1 2 1 2 1 2 2 3 2 3 3 3 3 3 3 6 7 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 */
T2: 将军令
n<=1e5,k<=20
分析:
从底向上贪心,对于节点最深的点x,选择距离它k的祖先覆盖它一定是最优的。
原因:距离它k的祖先是恰好覆盖x,且覆盖的范围最广的点。
自底向上选点,暴力打标记即可。
#include<bits/stdc++.h> using namespace std; #define N 100005 #define ri register int int tot=0,to[N<<1],nex[N<<1],head[N],fa[N],dep[N],n,kk,vis[N]; struct node{ int dep,u; }; priority_queue<node> q; bool operator < (const node &a,const node &b) { return a.dep<b.dep; } void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; } void dfs1(int u,int ff) { q.push((node){ dep[u],u }); for(ri i=head[u];i;i=nex[i]){ int v=to[i]; if(v==ff) continue; fa[v]=u; dep[v]=dep[u]+1; dfs1(v,u); } } int find(int x) { if(dep[x]<=kk) return 1; int cnt=1; while(cnt<=kk){ x=fa[x]; cnt++; } return x; } void dfs2(int rt,int u,int ff,int cnt) { if(cnt>kk) return ; vis[u]=1; for(ri i=head[u];i;i=nex[i]){ int v=to[i]; if(v==ff) continue; dfs2(rt,v,u,cnt+1); } } void work() { int ans=0; while(!q.empty()){ int u=q.top().u; q.pop(); if(vis[u]) continue; int k_fa=find(u); dfs2(k_fa,k_fa,0,0); ans++; } printf("%d\n",ans); } int main() { freopen("general.in","r",stdin); freopen("general.out","w",stdout); int t; scanf("%d%d%d",&n,&kk,&t); int a,b; for(ri i=1;i<=n-1;++i) scanf("%d%d",&a,&b),add(a,b),add(b,a); if(kk==0) { printf("%d\n",n); return 0; } dfs1(1,0); work(); return 0; } /* 4 1 0 1 2 1 3 1 4 8 1 2 1 2 1 3 1 4 1 5 2 6 2 7 3 8 6 1 0 1 2 1 3 1 4 4 5 4 6 16 1 0 1 2 1 3 1 4 2 5 2 6 2 7 3 8 4 9 5 10 10 11 3 12 9 13 11 14 11 15 11 16 */
T3: 星空
分析:
先将区间修改转换成单点修改,就像差分一样,这里的差分是将相邻的两个异或。
注意开头和结尾默认是1
例如样例:(1)01110(1)
差分后: 11001
然后随便选取一段区间,看区间翻转对应到原序列里是怎么变换的。例如翻转2~4这一段区间:
(1)00000 -> 10000
可以发现,是对l和r+1这两个位置进行了取反。
所以要加入n+1这一个位置!!
最终目标:差分序列为全0(原序列就是全1了)
可以发现,每次翻转一个区间,最多对两个位置的1有贡献。
所以可以将差分序列有1的地方提出来,做一遍bfs,就可以求出每个1和其他1同时被翻转成0所需要的最小步数。
再做一遍状压,求出将所有1翻转成0的贡献即可。
注意:求dis的时候一定要记得memset,因为有可能出现两个1之间不能翻转的情况,此时代价是正无穷。
#include<bits/stdc++.h> using namespace std; #define N 40005 #define ri register int int n,k,m,vis[N],dis[20][N],tot=0,pos[N],s[N],num[N],pre[N],b[N],dp[1<<20]; queue<int> q; void bfs(int u,int *dis)//不能打成 &dis[] { memset(vis,0,sizeof(vis)); dis[u]=0; q.push(u); vis[u]=1; while(!q.empty()){ int x=q.front(); q.pop(); for(ri i=1;i<=m;++i){ int y=x-b[i]; if(y>=1 && !vis[y]) dis[y]=dis[x]+1,q.push(y),vis[y]=1; y=x+b[i]; if(y<=n+1 && !vis[y]) dis[y]=dis[x]+1,q.push(y),vis[y]=1; } } //for(ri i=1;i<=n+1;++i) printf("%d ",dis[i]); //printf("\n"); } int main() { //freopen("starlit.in","r",stdin); //freopen("starlit.out","w",stdout); scanf("%d%d%d",&n,&k,&m); for(ri i=0;i<=N-4;++i) s[i]=1;//注意一定要记得把n+1的位置赋成 1 相当于添加了一个位置 操作l~r 变成l~r+1 for(ri i=1;i<=k;++i) scanf("%d",&pos[i]),s[pos[i]]=0; for(ri i=1;i<=m;++i) scanf("%d",&b[i]); for(ri i=1;i<=n+1;++i) pre[i]=s[i]^s[i-1]; memset(dis,0x3f3f3f,sizeof(dis));//一定要memset!! 因为可能两个1之间不能翻转 而不memset的话 翻转的代价会变成 0 for(ri i=1;i<=n+1;++i) if(pre[i]){//for到n+1 num[++tot]=i;//记录1所在的位置 bfs(i,dis[tot]);//求出从这个位置与其他 1同时变成 0 所花费的最小次数 } memset(dp,0x3f3f3f,sizeof(dp)); dp[0]=0; for(ri i=0;i<(1<<tot);++i){ int x=-1; for(ri j=0;j<tot;++j) if(!(i&(1<<j))) { x=j; break; }//找到第一个还没翻转的位置 for(ri j=x+1;j<tot;++j)//枚举第二个还没翻转的位置 if(!( i&(1<<j) )){ int nex=i|(1<<x)|(1<<j); dp[nex]=min(dp[nex],dp[i]+dis[x+1][num[j+1]]); } } printf("%d\n",dp[(1<<tot)-1]); return 0; } /* 5 2 2 1 5 3 4 5 2 2 1 2 2 3 */