Noip模拟10 2021.6.27
T1 入阵曲
好了,又一个考试败笔题。
也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数, 真是奇妙无比呢。
不过, 小 F 现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小问题。
题目清奇的叙述i引起小马清奇的思路——矩阵快速幂优化dp。于是开始了推柿子。。。
一小时,两小时,可恶,还没推出来,唉出来了。。等等,不对。。。
两个半小时将近三小时的时候,算了打暴力吧。。
然后就,唉。
可是这题并非矩阵乘法,草。。。。
那他疯狂diss我干嘛~~
60分很好拿到,不过当时太慌就没打前缀和,n^6暴力直接跑了,惨淡45。。。
正解就是在60暴力上加了一个优化。
he[k]表示两行之间的矩形的前缀和,num[k]表示出现的矩形的对数(记录有几对)。
因为,在模k意义下的前缀和,任取两个矩形前缀和相减必定是k的倍数。
num也就是统计模k相同的矩阵的个数。
1 #include<bits/stdc++.h> 2 #define write(X) printf("%lld",X) 3 #define read(X) scanf("%lld",&X) 4 #define rint register long long 5 #define int long long 6 using namespace std; 7 8 const int NN=1e6+10; 9 int n,m,p,rp; 10 int num[NN],he[NN]; 11 int sum[405][405],a[405][405]; 12 13 inline void init(){ 14 read(n); read(m); read(p); 15 for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) 16 read(a[i][j]),a[i][j]%=p; 17 for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) 18 sum[i][j]=(sum[i][j-1]+a[i][j])%p; 19 for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) 20 sum[i][j]=(sum[i][j]+sum[i-1][j])%p; 21 } 22 23 namespace WSN{ 24 inline int main(){ 25 init(); 26 for(rint i=0;i<n;i++) 27 for(rint j=i+1;j<=n;j++){ 28 num[0]=1; 29 for(rint k=1;k<=m;k++){ 30 he[k]=(sum[j][k]-sum[i][k]+p)%p; 31 rp+=num[he[k]]++; 32 } 33 for(int k=1;k<=m;k++) num[he[k]]=0; 34 } 35 write(rp); putchar('\n'); 36 return 0; 37 } 38 } 39 signed main(){return WSN::main();}
T2 将军令
题目猛一看,这不就是小胖守皇宫吗?不过K的值更大就不会考虑了。。
其实,应该考虑贪心。
从深度最深的点开始向上找他的K级父亲,这样的话从K级父亲开始将能够管到的点全部标记,找就可以了。
1 #include<bits/stdc++.h> 2 #define r(X) scanf("%d",&X) 3 #define w(X) printf("%d\n",X) 4 #define Min(A,B) ((A)<(B)?(A):(B)) 5 #define rint register int 6 using namespace std; 7 8 const int NN=1e5+10; 9 int n,k,t,d[NN],fa[NN][25],ans; 10 bool vis[NN]; 11 struct SNOW{int to,next;}; SNOW e[NN<<1]; int head[NN],tot; 12 inline void add(int x,int y){ e[++tot]=(SNOW){y,head[x]}; head[x]=tot;} 13 struct snow{ 14 int dep,id; 15 }; snow m[NN]; 16 inline bool cmp(snow a,snow b){return a.dep>b.dep;} 17 18 inline void dfs1(int f,int x){ 19 for(rint i=head[x];i;i=e[i].next){ 20 int y=e[i].to; 21 if(y==f) continue; 22 m[y].dep=m[x].dep+1; fa[y][1]=x; 23 for(rint j=2;j<=Min(k,m[y].dep);j++) fa[y][j]=fa[fa[y][j-1]][1]; 24 dfs1(x,y); 25 } 26 } 27 28 inline void dfs2(int pre,int x,int tmp){ 29 vis[x]=1; 30 if(!tmp) return; 31 for(rint i=head[x];i;i=e[i].next){ 32 int y=e[i].to; 33 if(y==pre) continue; 34 dfs2(x,y,tmp-1); 35 } 36 } 37 38 namespace WSN{ 39 inline int main(){ 40 r(n); r(k); r(t); 41 for(rint i=1,x,y;i<n;i++) 42 r(x),r(y),add(x,y),add(y,x),m[i].id=i; 43 if(k==0) {w(n);return 0;} 44 m[n].id=n; 45 dfs1(0,1); 46 sort(m+1,m+n+1,cmp); 47 for(rint i=1;i<=n;i++) if(!vis[m[i].id]){ 48 rint f=fa[m[i].id][k]; 49 ans++,dfs2(-1,f,k); 50 } 51 w(ans); 52 return 0; 53 } 54 } 55 signed main(){return WSN::main();}
T3 星空
此题思维量较大,融合状压,最短路,差分思想于一体。
记录1为灭,0为开。
首先可以看到操作是在一段上取反,可以用差分O(1)进行修改,于是我们先维护一个差分数组nu(注意n++,以为要把最后一个数的差分记上)。这样,这个0/1串差分数组上就有不超过2×k个1(因为原数组中每出现一个1,差分数组中要可能出现两个)。
这样问题转化为:
需要从一串数上取间隔为b[i]的两个数进行取反,问最少多少次把整个串变成0
然后我们再看,如果把每次操作视为一次移动,即把一段长度为b[i]左端点的数移动到右端点+1并与之相抑或。若两者都是1,则相消,若一个是1,一个是0,则可看作移动,两个都是0,则可以看作不动。这样,问题转化成:
在一个有n个节点的无向图中,每个节点连m条边,你要找到每一个1移动到另一个1的最短距离,求总的最短距离
这样的话可以用spfa(其实直接bfs也可以,以为不用去再更新之前的点的距离)求最短路。预处理出每个最短路因此我们可以发现用状压比较好解决,压的是不超过2×k个1的状态。
再提一下,在进行状压的时候,不用顾及前面枚举过的点,那些点的值已经处理好了,因此直接先找到你要取的点,即NUM1+1,从这一个开始枚举后面的是1的点就行。
1 #include<bits/stdc++.h> 2 #define r(X) scanf("%d",&X) 3 #define w(X) printf("%d\n",X) 4 using namespace std; 5 6 int n,k,m,b[70]; 7 bool a[60005],vis[60005]; 8 bool nu[60005];//差分 9 queue<int> q; 10 vector<int> sh; 11 int dis[17][60005];//一维只开为1的点 12 int dp[1<<18]; 13 inline void bfs(){ 14 for(int i=0;i<sh.size();i++){ 15 int st=i,dian=sh[i]; 16 for(int i=0;i<=n;i++) vis[i]=0; 17 dis[st][dian]=0; vis[st]=true; q.push(dian); 18 while(!q.empty()){ 19 int x=q.front(); q.pop(); vis[x]=false; 20 for(int i=1;i<=m;i++){ 21 int y1=x+b[i],y2=x-b[i]; 22 if(y1<=n){ 23 if(dis[st][y1]>dis[st][x]+1){ 24 dis[st][y1]=dis[st][x]+1; 25 if(!vis[y1]) vis[y1]=true,q.push(y1); 26 } 27 } 28 if(y2>0){ 29 if(dis[st][y2]>dis[st][x]+1){ 30 dis[st][y2]=dis[st][x]+1; 31 if(!vis[y2]) vis[y2]=true,q.push(y2); 32 } 33 } 34 } 35 } 36 } 37 } 38 39 namespace WSN{ 40 inline int main(){ 41 r(n); r(k); r(m); 42 for(int i=1,x;i<=k;i++) r(x),a[x]=1; 43 for(int i=1;i<=m;i++) r(b[i]); 44 n++; 45 for(int i=1;i<=n;i++){ 46 nu[i]=a[i]^a[i-1]; 47 if(nu[i]) sh.push_back(i); 48 } 49 int pot=sh.size(); 50 for(int i=0;i<=pot;i++) for(int j=0;j<=n;j++) 51 dis[i][j]=99999999; 52 bfs(); 53 int ti=(1<<pot)-1; 54 memset(dp,0x3f,sizeof(dp)); 55 dp[0]=0; 56 for(int sta=0;sta<=ti;sta++){ 57 int num1=0; while((sta&(1<<num1))) num1++; 58 for(int i=num1+1;i<=pot;i++){ 59 if(!(sta&(1<<i))) dp[sta|1<<i|1<<num1]=min(dp[sta|1<<i|1<<num1],dp[sta]+dis[num1][sh[i]]); 60 } 61 } 62 w(dp[ti]); 63 return 0; 64 } 65 } 66 signed main(){return WSN::main();}
总结一下这次打挂:
先打暴力,在没打暴力的时候想正解显然是非常弱智的行为(由杠哥大定理可得),
还有就是别被题目的无关题干骗了,觉得是啥就打啥。。。