8.11题解

T1[洛谷P3941]

60分

考场想到了前缀和,二维树状数组都打出来了,应是没想出来前缀和怎么维护?我当时脑子估计是死掉了,最后明明是$O(n^4)$的思路,愣是给自己加了个$log$,考场上太蠢,我能怎么办呢

100分

我们会发现我们如果需要求一个子矩阵的和,我们不可避免的需要枚举左上角和右下角,但事实上有很多东西都是重复枚举的,有没有什么可以避免这种情况的方法呢?我们想一下,如果我们之枚举行的上下边界以及列的右边界,有没有可能可以搞出我们的子矩阵呢?我们思考一下,对于两个上下边界相同,左边界均为一的两个子矩阵,如果这两个矩阵中间的部分刚好是k的倍数,会发生什么呢?很显然,中间那一块的区间和在$\%k$的意义下为0,也就是说这两个子矩阵的区间和在$\%k$意义下是相等的,那我们可以枚举上下边界,以及右边界,开个桶,把$\%k$意义下的区间和丢进去,那么在同一个桶中的元素随便选两个,中间就是一个合法矩阵,所以答案就是$sum{C_{sum}^{2}}$,接下来我们考虑一种特殊情况,$\%k$意义下区间和为0,那事实上他们既可以两两之间包含合法区间,而他们自己也是一个合法区间所以多加一个桶中元素个数即可,当然也可以初始化$tong[0]=1$

 1 #include<iostream>
 2 #include<cstdio>
 3 #define maxn 410
 4 #define maxk 1001000
 5 #define int long long
 6 using namespace std;
 7 int n,m,k,ans,top;
 8 int tong[maxk],s[maxn];
 9 int a[maxn][maxn],qz[maxn][maxn];
10 signed main()
11 {
12     scanf("%lld%lld%lld",&n,&m,&k);
13     for(int i=1;i<=n;++i)
14         for(int j=1;j<=m;++j)
15         {
16             scanf("%lld",&a[i][j]);  a[i][j]=a[i][j]%k;
17             qz[i][j]=(((qz[i-1][j]+qz[i][j-1])%k-qz[i-1][j-1]+k)%k+a[i][j])%k;
18         }
19     for(int i=1;i<=n;++i)//上界
20     {
21         for(int j=i;j<=n;++j)//下界
22         {
23             tong[0]=1;
24             for(int p=1;p<=m;++p)//右侧
25             {
26                 s[++top]=(qz[j][p]-qz[i-1][p]+k)%k;
27                 tong[s[top]]++;
28             }
29             while(top)
30             {
31                 ans+=tong[s[top]]*(tong[s[top]]-1)/2;
32                 tong[s[top]]=0;  top--;
33             }
34         }
35     }
36     printf("%lld
",ans);
37     return 0;
38 }
View Code

T2[洛谷P3942]

又是一道贪心,最近考场上就没打出来过贪心,以后考试的时候如果想不出什么来就想想贪心,还是老样子,证明贪心的正确性,对于一个叶子节点,我们考虑在他的$k$级父亲,或$k$级以下父亲放小队哪个更优?显然是$k$级父亲,因为$k$级父亲向上可覆盖的点更多,贡献更大,所以说贪心是正确的,当然了这道题dp可做,可以参照小胖守皇宫一题,是道树形dp,百度可搜

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<queue>
 5 #define maxn 100100
 6 using namespace std;
 7 int n,k,t,js,ans;
 8 int fa[maxn],head[maxn],to[maxn*2],xia[maxn*2],pd[maxn],deep[maxn];
 9 struct node{
10     int dep,pos;
11 };
12 bool operator < (const node &a,const node &b)
13 {
14     return a.dep<b.dep;
15 }
16 priority_queue <node> que;
17 void add(int x,int y)
18 {
19     to[++js]=y;  xia[js]=head[x];  head[x]=js;
20 }
21 void dfs(int x)
22 {
23     que.push((node){deep[x],x});    
24     for(int i=head[x];i;i=xia[i])
25         if(!fa[to[i]])  {fa[to[i]]=x;  deep[to[i]]=deep[x]+1;  dfs(to[i]);}
26 }
27 void Dfs(int x,int jl)
28 {
29     if(jl>k)  return ;
30     pd[x]=1;
31     for(int i=head[x];i;i=xia[i])
32         if(fa[to[i]]==x)  Dfs(to[i],jl+1);
33 }
34 int main()
35 {
36     scanf("%d%d%d",&n,&k,&t);
37     for(int i=1;i<n;++i)  {int u,v;  scanf("%d%d",&u,&v);  add(u,v);  add(v,u);}
38     if(k==0)  {printf("%d
",n);  return 0;}
39     fa[1]=-1;  deep[1]=1;  dfs(1);
40     while(que.size())
41     {
42         node ls=que.top();  que.pop();
43         int js=0,d=ls.pos;
44         if(pd[d]==1)  continue;
45         while(1)
46         {
47             if(js==k)  break;
48             if(fa[d]==-1)  break;
49             js++;  d=fa[d];
50         }
51         ans++;  js=0;
52         while(1)
53         {
54             Dfs(d,js);
55             if(js==k)  break;
56             if(fa[d]==-1)  break;
57             js++;  d=fa[d];
58         }
59     }
60     printf("%d
",ans);
61     return 0;
62 }
View Code

T3[洛谷P3943]

思维含量,考察的知识面各方面都比较广

预备知识:最短路,差分,状压

首先对于这种区间修改的东西,一定要先去联想差分,可以把$O(n)$变成$O(1)$,非常好用,设$a[]$为原数组,$b[]$为差分数组,$b[i]=a[i]xora[i-1]$,对于$a$数组,我们规定点亮为0,没点亮为1,$a[0]=0$,那最后我们只需要整个差分数组都为0,也就是都与$a[0]$相等,由于差分会用到$r+1$,所以说$b$数组下标比$a$数组大一,此时对于$a$数组的区间修改就变为了对$b$数组的单点修改,也就是选一个距离满足给定距离的两个点,取反,最多多少次可以全变0,我们的目的肯定是选两个1变成0,这过程中当然会不尽人意,需要改变0,但是0只要变两次就回来了,那我们给可同时取反的点之间连边,去跑每两个1之间的最短路,即可用最少的次数把两个1变0,变零之后我们怎么知道哪两个作为一组呢?我们会发现差分数组中1最多有$2*k$个,也就是最多16个,那我们可以状压啊,所以状压一下就结束了

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<queue>
 5 #define maxk 9
 6 #define maxm 70
 7 #define maxn 40100
 8 using namespace std;
 9 int n,k,m,js,jj,top;
10 int head[maxn],to[maxn*maxm*2],xia[maxn*maxm*2],c[maxn];
11 int a[maxn],b[maxn],mm[maxm],f[1<<(2*maxk+1)],dis[maxn],pd[maxn];
12 int di[maxk*2][maxk*2];
13 queue <int> s;
14 void add(int x,int y)
15 {
16     to[++jj]=y;  xia[jj]=head[x];  head[x]=jj;
17 }
18 void SPFA(int x)
19 {
20     memset(dis,0x3f,sizeof(dis));
21     dis[x]=0;  pd[x]=1;  top=0;  s.push(x);
22     while(s.size())
23     {
24         int ls=s.front();  s.pop();
25         for(int i=head[ls];i;i=xia[i])
26         {
27             int lss=to[i];
28             if(dis[lss]>dis[ls]+1)
29             {
30                 dis[lss]=dis[ls]+1;
31                 if(!pd[lss])  {s.push(lss);  pd[lss]=1;}
32             }
33         }
34         pd[ls]=0;
35     }
36 }
37 int main()
38 {
39     scanf("%d%d%d",&n,&k,&m);
40     memset(f,0x3f,sizeof(f));  memset(di,0x3f,sizeof(di));  f[0]=0;
41     for(int i=1;i<=k;++i)  {int x;  scanf("%d",&x);  a[x]=1;}
42     for(int i=1;i<=m;++i)  scanf("%d",&mm[i]);
43     for(int i=1;i<=n+1;++i)
44     {
45         b[i]=a[i]^a[i-1];
46         if(b[i]==1)  c[++js]=i;
47     }
48     /*for(int i=1;i<=n;++i)
49         for(int j=1;j<=m;++j)
50         {
51             if(i+mm[j]>n+1)  continue;
52             add(i,i+mm[j]);  add(i+mm[j],i);
53         }*/
54     for(int i=1;i<=n+1;++i)
55     {
56         for(int j=1;j<=m;++j)
57         {
58             if(i-mm[j]>=0)  add(i,i-mm[j]);
59             if(i+mm[j]<=n+1)  add(i,i+mm[j]);
60         }
61     }
62     for(int i=1;i<=js;++i)
63     {
64         SPFA(c[i]);
65         for(int j=i+1;j<=js;++j)  {di[i-1][j-1]=dis[c[j]];  di[j-1][i-1]=dis[c[j]];}
66     }
67     for(int i=0;i<(1<<js);++i)
68     {
69         int qd=-1,j=0;
70         while(j<js)
71         {
72             if(!(i&(1<<j)))
73             {
74                 if(qd==-1)  qd=j;
75                 else  f[i|(1<<qd)|(1<<j)]=min(f[i|(1<<qd)|(1<<j)],f[i]+di[qd][j]);
76             }
77             j++;
78         }
79     }
80     printf("%d
",f[(1<<js)-1]);
81     return 0;
82 }
View Code

如果想快一点的话,说不定可以试试堆优化$dijkstra$

原文地址:https://www.cnblogs.com/hzjuruo/p/11336847.html