Loading

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();}
View Code

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();}
View Code

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();}
View Code

总结一下这次打挂:

先打暴力,在没打暴力的时候想正解显然是非常弱智的行为(由杠哥大定理可得),

还有就是别被题目的无关题干骗了,觉得是啥就打啥。。。

posted @ 2021-06-28 15:39  雪域亡魂  阅读(64)  评论(0编辑  收藏  举报