NOIP模拟测试17「入阵曲·将军令·星空」
入阵曲
题解
应用了一种美妙移项思想,
我们先考虑在一维上的做法
维护前缀和$(sum[r]-sum[l-1])\%k==0$可以转化为
$sum[r]\% k==sum[l-1]\%k$开个桶维护一下即可
然后拓展到二维上
把两行之间所有行拍扁看作一维上的区间,
我们枚举两行和行之间所有列开个桶维护
$n^2 m$复杂度
for(ll i=1;i<=n;i++) for(ll j=1;j<=i;j++){ flag[0]=1; for(ll q=1;q<=m;q++){ t[q]=(sum[i][q]-sum[j-1][q]+k)%k; cnt+=flag[t[q]]; flag[t[q]]++; } for(ll q=1;q<=m;q++) flag[t[q]]=0; }
至于桶里flag[0]=1初始化含义
当你其他%==0的矩阵放进去时本身就符合条件,在桶里找配对之前就构成合法矩阵,这样做统计了所有%=0的情况
当然这样也行
for(ll i=1;i<=n;i++) for(ll j=1;j<=i;j++){ for(ll q=1;q<=m;q++){ t[q]=(sum[i][q]-sum[j-1][q]+k)%k; cnt+=flag[t[q]]; flag[t[q]]++; } cnt+=flag[0]; for(ll q=1;q<=m;q++) flag[t[q]]=0; }
将军令
题解
$45\%$算法
计算k==1
和小胖收皇宫类似,
思考dp数组含义
$f[x][0]$表示被父亲管辖 $f[x][1]$表示被自己管辖 $f[x][2]$表示被自己儿子管辖
f数组转移
若x被父亲管辖,那么儿子必须被自己管辖或者被儿子的儿子管辖
$f[x][0]=\sum\limits_{y}^{y\in son}min(f[y][1],f[y][2])$
若x被自己管辖,那么x转移随意
$f[x][1]=\sum\limits_{y}^{y\in son} min(f[y][1],f[y][2],f[y][0])$
若x被儿子管辖,那么儿子可以被自己管辖或者被儿子的儿子管辖
但如果所有儿子的儿子代价都比选自己代价小,我们需要强制选出一个$f[y][1]-f[y][2]$差值最小的更新
代码稍微体会一下
void dp(ll x){ if(!son[x]) { f[x][1]=1; f[x][2]=0x7ffffff; f[x][0]=0; return ; } vis[x]=1; ll sum=0,sum2=0,sum3=0,pan=0,mix=0x7ffffff; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dp(y); sum+=min(f[y][0],min(f[y][1],f[y][2])); sum2+=min(f[y][2],f[y][1]); sum3+=min(f[y][1],f[y][2]); if(f[y][1]<=f[y][2]) pan=1; else mix=min(mix,f[y][1]-f[y][2]); } f[x][1]=sum+1; f[x][0]=sum2; if(pan==1) f[x][2]=sum3; else f[x][2]=sum3+mix; }
$75\%$算法
计算k==2
还是思考dp数组含义
如果还是像上一个那样定义要写死
换一种0表示被自己守,1表示被儿子守(至少选了一个儿子),2被孙子守(至少选了一个孙子),3子孙全被覆盖自己没有,4孙子全被覆盖(儿子可以被覆盖可以不被覆盖)自己没有
还是思考转移
被自己守x转移还是随意$f[x][0]=\sum\limits_{y}^{y\in son} min(f[y][0],f[y][1],f[y][2],f[y][3],f[y][4])$
被儿子守x转移比较复杂
儿子需要自保,因为儿子可以管辖自己兄弟,所以随意选,所以可以选到$f[y][3]$
$f[x][1]=\sum\limits_{y}^{y\in son} min(f[y][0],f[y][1],f[y][2],f[y][3])$
也和k==1类似,显然我们还是要选出来一个最小差值
被自己孙子守,那么自己儿子需要自保,$f[y][1],f[y][2],f[y][0]$都满足条件
子孙全被覆盖自己没有,那么就是儿子孙子全被覆盖,儿子本身也要被覆盖,
$f[y][0]$ $f[y][1]$ $f[y][2]$ (显然$f[y][3]$不行)
孙子全被覆盖,那么就是孙子全被覆盖,那么可以是$f[y][3]$,$f[y][1]$,$f[y][0]$,$f[y][2]$
代码稍微体会一下(这个代码有误,我并不能调出来)
void dp2(ll x){ //1表示被自己守,2表示被儿子,3被孙子,4子孙全有自己无,5孙子全有自己无 if(!son[x]){ f2[x][4]=0x7fffffff; f2[x][5]=0x7fffffff; f2[x][1]=1; f2[x][2]=0; f2[x][3]=0; return ; } vis[x]=1; ll sum1=0,sum2=0,sum3=0,sum4=0,sum5=0,pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dp2(y); sum1+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],min(f2[y][4],f2[y][5]))); sum2+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],f2[y][4])); if(f2[y][1]<=f2[y][2]&&f2[y][1]<=f2[y][3]&&f2[y][1]<=f2[y][4]) pan=1; else mix1=min(mix1,f2[y][1]-min(f2[y][2],min(f2[y][3],f2[y][4]))); sum3+=min(f[y][1],min(f[y][2],f[y][3])); if(f2[y][2]<=f2[y][1]&&f2[y][2]<=f2[y][3]) pan2=1; else mix2=min(mix2,f2[y][2]-min(f2[y][1],f2[y][3])); sum4+=min(min(f2[y][1],f2[y][2]),f2[y][3]); sum5+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],f2[y][4])); } f2[x][1]=1+sum1; if(pan==1) f2[x][2]=sum2; else f2[x][2]=sum2+mix1; if(pan2==1) f2[x][3]=sum3; else f2[x][3]=sum3+mix2; f2[x][4]=sum4; f2[x][5]=sum5; }
思考怎么优化
dp数组含义再次转变
$f[x][w]$表示$f$中<=w最小的数
例如$f[x][4]$表示$min(f[x][1],f[x][2],f[x][3],f[x][4],f[x][0])$
转移类似
$f[x][0]=\sum\limits_{y}^{y\in son} f[y][4] $$ +1$
$f[x][3]=\sum\limits_{y}^{y\in son} f[y][2] $
$f[x][2]=\sum\limits_{y}^{y\in son} f[y][3] $$+差值$
$f[x][4]=\sum\limits_{y}^{y\in son} f[y][3] $
$f[x][1]=\sum\limits_{y}^{y\in son} f[x][4] $$+差值$
最后1 2 3 4 互相取min具体看代码
再次感受一下
void dp2(ll x){ if(!son[x]){ f2[x][0]=1; f2[x][1]=f2[x][2]=1;f2[x][3]=f2[x][4]=0; f2[x][1]=0x7fffffff; return ; } vis[x]=1; f2[x][0]=1; f2[x][1]=f2[x][2]=f2[x][3]=f2[x][4]=0; ll pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dp2(y); f2[x][0]+=f2[y][4]; f2[x][3]+=f2[y][2]; f2[x][4]+=f2[y][3]; mix1=min(f2[y][0]-f2[y][3],mix1); mix2=min(f2[y][1]-f2[y][2],mix2); } f2[x][1]=f2[x][4]+mix1; f2[x][2]=min(f2[x][3]+mix2,min(f2[x][0],f2[x][1])); f2[x][3]=min(f2[x][3],f2[x][2]); f2[x][4]=min(f2[x][3],f2[x][4]); // printf("mix1=%lld 2=%lld x=%lld f[][1]=%lld [2]=%lld [3]=%lld [4]=%lld [0]=%lld\n",mix1,mix2,x,f2[x][1],f2[x][2],f2[x][3],f2[x][4],f2[x][0]); }
$100\%$算法
dp我是打不出来
应该会有别的大神打出来dp100分
那么正解是什么神奇的算法呢?
简单贪心!
啊啊啊啊啊又是贪心,我又没有看出来它是贪心,awsl,我太菜了
步骤分比正解难的多得多
贪心10分钟改完AC,每次找到最深得节点找它的k级父亲
实现不要想复杂,暴力改,暴力跳即可
你会发现你比dp还要快!!!!!!!!!!!!!!!!!!!
#include<bits/stdc++.h> using namespace std; #define ll long long #define A 1010101 ll n,k,t,tot=0,sum1=0,sum2=0,sumji=0,sumou=0,skriller=0; ll head[A],nxt[A],ver[A],deep[A],son[A],f[A][3],fa[A]; ll f2[A][10]; struct node{ ll deep,x; friend bool operator < (node a,node b){ return a.deep<b.deep; } }point[A]; priority_queue<node> q; bool vis[A]; void add(ll x,ll y){ nxt[++tot]=head[x]; head[x]=tot; ver[tot]=y; } ll read(){ ll f=1,x=0;char c=getchar(); while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); } while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); } return f*x; } void dfs(ll x,ll de){ vis[x]=1; point[x].deep=de; point[x].x=x; q.push(point[x]); for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dfs(y,de+1); son[x]++; fa[y]=x; } } void dp(ll x){ if(!son[x]) { f[x][1]=1; f[x][2]=0x7ffffff; f[x][0]=0; return ; } vis[x]=1; ll sum=0,sum2=0,sum3=0,pan=0,mix=0x7ffffff; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dp(y); sum+=min(f[y][0],min(f[y][1],f[y][2])); sum2+=min(f[y][2],f[y][1]); sum3+=min(f[y][1],f[y][2]); if(f[y][1]<=f[y][2]) pan=1; else mix=min(mix,f[y][1]-f[y][2]); } f[x][1]=sum+1; f[x][0]=sum2; if(pan==1) f[x][2]=sum3; else f[x][2]=sum3+mix; } ll find(ll x){ ll w=1; while(w<=k){ w++; x=fa[x]; } return x; } void dp2(ll x){ if(!son[x]){ f2[x][0]=1; f2[x][1]=f2[x][2]=1;f2[x][3]=f2[x][4]=0; f2[x][1]=0x7fffffff; return ; } vis[x]=1; f2[x][0]=1; f2[x][1]=f2[x][2]=f2[x][3]=f2[x][4]=0; ll pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(vis[y]) continue; dp2(y); f2[x][0]+=f2[y][4]; f2[x][3]+=f2[y][2]; f2[x][4]+=f2[y][3]; mix1=min(f2[y][0]-f2[y][3],mix1); mix2=min(f2[y][1]-f2[y][2],mix2); } f2[x][1]=f2[x][4]+mix1; f2[x][2]=min(f2[x][3]+mix2,min(f2[x][0],f2[x][1])); f2[x][3]=min(f2[x][3],f2[x][2]); f2[x][4]=min(f2[x][3],f2[x][4]); // printf("mix1=%lld 2=%lld x=%lld f[][1]=%lld [2]=%lld [3]=%lld [4]=%lld [0]=%lld\n",mix1,mix2,x,f2[x][1],f2[x][2],f2[x][3],f2[x][4],f2[x][0]); } void dfs2(ll x,ll fa,ll de){ vis[x]=1; if(de==k) return ; for(ll i=head[x];i;i=nxt[i]){ ll y=ver[i]; if(y==fa) continue; dfs2(y,x,de+1); } } int main(){ n=read(),k=read(),t=read(); // printf("%lld\n",k); if(k==0){ for(ll i=1,a,b;i<=n-1;i++){ a=read(),b=read(); } printf("%lld\n",n); return 0; } /* if(k==1){ for(ll i=1,a,b;i<=n-1;i++){ a=read(),b=read(); add(a,b); add(b,a); } dfs(1,1); memset(vis,0,sizeof(vis)); dp(1); printf("%lld\n",min(f[1][1],f[1][0])); return 0; } */ if(k==2){ memset(f2,0x3f,sizeof(f2)); for(ll i=1,a,b;i<=n-1;i++){ a=read(),b=read(); add(a,b); add(b,a); } dfs(1,1); memset(vis,0,sizeof(vis)); dp2(1); printf("%lld\n",f2[1][2]); return 0; } else{ for(ll i=1,a,b;i<=n-1;i++){ a=read(),b=read(); add(a,b); add(b,a); } dfs(1,1); memset(vis,0,sizeof(vis)); while(!q.empty()){ ll x=q.top().x; q.pop(); if(vis[x]) continue; ll f=find(x); // printf("x=%lld f=%lld\n",x,f); dfs2(f,0,0); skriller++; } printf("%lld\n",skriller); } }
星空
题解
首先如果翻转我们楞翻转一次复杂度最高n那么考虑优化
我们将取反转化为异或
思考$1 xor 1=0$
$0 xor 1=1$
那么取反我们就转化为了$xor 1$
定义差分数组为$b[i]=a[i] xor a[i+1]$
整段区间$xor1$差分(第一次看到异或的差分),我们转化为$l xor1$ $r+1xor1$
我们不可能白白翻转一段全是$1$的
我们翻转至少有$1$个零
翻转两端都有$0$那么就可以看作消去
两端只有一端有$0$那么可以看作移动
那么问题就转化为了如何最少移动消去使所有$0$变为$1$
处理出任意两个点之间消去代价(可以完全背包,把每个操作换成$+ $,$-$ 两个代价)
for(ll i=1;i<=m;i++) for(ll j=a[i];j<=n;j++) d[j]=min(d[j-a[i]]+1,d[j]); for(ll i=1;i<=m;i++) for(ll j=n-a[i];j;j--) d[j]=min(d[j+a[i]]+1,d[j]);
考虑k很小,然后状压解决把所有点消去代价
memset(f,0x7f,sizeof(f)); f[0]=0; for(ll i=0;i<ci[cnt];i++){ for(ll j=0;j<cnt;j++){ if(!(ci[j]&i)){ for(ll k=j+1;k<cnt;k++){ if((!(ci[k]&i))){ if(f[i]>100000000) continue; else { // printf(" i=%lld j=%lld k=%lld cij=%lld cik=%lld f=%lld\n",i,j,k,ci[j],ci[k],f[i]); f[i|ci[j]|ci[k]]=min(f[i|ci[j]|ci[k]],f[i]+d[abs(pos[j]-pos[k])]); // printf("f=%lld d=%lld\n",f[i],d[abs(pos[j]-pos[k])]); } } } break; } } }
代码
#include<bits/stdc++.h> using namespace std; #define ll long long #define A 111111 ll b[A],d[A],Xor[A],pos[A],ci[A],f[A],a[A]; ll cnt,m,n,k; inline void init() { memset(d,0x3f,sizeof(d)); d[0]=0; for(ll i=1;i<=m;i++) for(ll j=a[i];j<=n;j++) d[j]=min(d[j-a[i]]+1,d[j]); for(ll i=1;i<=m;i++) for(ll j=n-a[i];j;j--) d[j]=min(d[j+a[i]]+1,d[j]); for(ll i=1;i<=n;i++){ if(Xor[i]) pos[cnt++]=i; } /* for(ll i=1;i<=n;i++){ printf("%lld\n",d[i]); } printf("cnt=%lld\n",cnt); */} inline void dp(){ memset(f,0x7f,sizeof(f)); f[0]=0; for(ll i=0;i<ci[cnt];i++){ for(ll j=0;j<cnt;j++){ if(!(ci[j]&i)){ for(ll k=j+1;k<cnt;k++){ if((!(ci[k]&i))){ if(f[i]>100000000) continue; else { // printf(" i=%lld j=%lld k=%lld cij=%lld cik=%lld f=%lld\n",i,j,k,ci[j],ci[k],f[i]); f[i|ci[j]|ci[k]]=min(f[i|ci[j]|ci[k]],f[i]+d[abs(pos[j]-pos[k])]); // printf("f=%lld d=%lld\n",f[i],d[abs(pos[j]-pos[k])]); } } } break; } } } } int main(){ ci[0]=1; for(ll i=1;i<=30;i++) ci[i]=ci[i-1]<<1/*,printf("ci[%lld]=%lld\n",i,ci[i])*/; scanf("%lld%lld%lld",&n,&k,&m); n++; for(ll i=1,ak;i<=k;i++){ scanf("%lld",&ak); b[ak]^=1; } for(ll i=1;i<=n;i++){ Xor[i]=b[i-1]^b[i]; } for(ll i=1;i<=m;i++){ scanf("%lld",&a[i]); } init(); dp(); printf("%lld\n",f[ci[cnt]-1]); }