• 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$

  • 相关阅读:
    【转】Java学习---HashMap的工作原理
    【转】Java学习---集合框架那些事
    Linux学习---linux下的彩蛋和各种有趣的命令
    【转】VMware虚拟机三种网络模式超详解
    沃顿商学院的MBA课程
    本杰明-富兰克林的13节制
    美学需要读的书
    芒格推荐书单
    回声消除(AEC)原理
    adc0和adc1
  • 原文地址:https://www.cnblogs.com/hzjuruo/p/11336847.html
Copyright © 2020-2023  润新知