NOIp ( on line ) 提高组 2020 总结


 

 

 


  T1 : 序列

    题意:

       一共有T组数据,每组数据有两个长度为n的序列a,b,m个操作,问a序列是否可以转换成b,是输出YES,否的话输出NO。

       m个操作分别为ti,xi,yi,若t为1,则x和y上的数可同时加减一;若t为2,则x上的数加一同时y上的数减一,或y上的数加一同时x上的数减一;

        

      大概理解为: 将a上的所有数变成b上的对应的数,当t=1时可以将a[x],a[y]同时加任何数,当t=2时可以将a[x]加任何数同时a[y]上的数减上相同的数(相当于把a[x]一部分值转到a[y]上);

       列:   a : 1 3 5 4     t=1,x=1,y=2         则若a[1] (1) 要加上3,a[2] (3) 也必须加上3,a 变成 4 6 5 4

                 t=2,x=1,y=2    则若a[1] (1) 要加上3,a[2] (3) 必须减去3,a变成 4 0 5 4;       

    数据范围:

         1≤T≤10,1≤n,m≤105,1≤ai,bi≤109


 

          

    分析:

        这题可以用图论来做

        先定义一个序列 c ,c[i]=a[i]-b[i],这样我们只要判断c中的每一个值是否可以变为零就行了。

        然后读入m个操作

        先不用管t为1时的情况,若 t 为2的话,把 x 和 y 之间连一条无向边,然后将图中的联通块缩点

        因为联通块中的任何俩个数都可以将他们的一部分值互相转换,所以可以将他们看做一个,该点的权值就是联通块中的每一个c[i]的和

        下面记 i 点所在的联通块为bel[i]

        接下来再操作将t为1时的情况, 将bel[x]与bel[y]之间连一条无向边,若bel[x]=bel[y]则用一个bool数组表示bel[x]有自环,有自环后bel[x]的值就可以成2的倍数增长

        接下来就对新图进行缩点,可以发现,若两个点之间的路径长度为偶数时,两个点间可进行值的转移

        所以我们再将图进行黑白染色同色点之间一定长度为偶数的路径,那么我们就将颜色相同的点进行缩点。

        缩到最后,我们再将判断一下每个联通块是否可以将其的总权值变为零,如果有一个联通块不能将其总权值变为零,则输出NO,反之输出YES。

        我们再看看如何判断联通块之间是否可以将其总权值变为零。

        因为缩点缩到最后一定每个联通块最多只有两个点(黑点和白点),所以判断一下这两个点可不可以变为零就行了。

        只要两个点的权值相同,那么就可以两个点同时加减变为零。

        如果不相等,如果两个点其中一个有自环,且两个点权值差为偶数,那就可以将两个点的值变相同,之后再进行操作。

        因为我们只需要判断联通块是否可以变成零就行了,所以不用建新图,dfs一下就行。

        (我还是太弱了,考场上不会写图,只会n≤2的)

 

 


 

    代码如下:

 

  1 #include<cstdio>
  2 #include<iostream>
  3 #include<algorithm>
  4 #include<cstring>
  5 #include<vector>
  6 
  7 using namespace std;
  8 
  9 typedef long long LL;
 10 
 11 const int N=1000010;
 12 
 13 LL sum[N];
 14 int bel[N],a[N],color[N];
 15 bool book[N];
 16 
 17 vector<int> h[N],g[N];
 18 
 19 struct Node
 20 {
 21     int x,y;
 22 }w[N];
 23 
 24 void dfs1(int u,int c)//第一次缩点 (t为2时的边) 
 25 {
 26     bel[u]=c;
 27     sum[c]+=a[u];
 28     for(int i=0;i<g[u].size();i++)
 29     {
 30         int j=g[u][i];
 31         if(!bel[j])
 32         dfs1(j,c);
 33     }
 34 }
 35 
 36 bool dfs2(int u,int col,bool &pd,LL &suma,LL &sumb)//第二次缩点 
 37 {
 38     if(~color[u]) return color[u]==col;//若颜色不一样说明有奇数环 
 39     
 40     color[u]=col;//进行染色 
 41     pd|=book[u];//判断有没有自环 
 42     
 43     if(col==0) suma+=sum[u];//将相同色的点权值相加 
 44     else sumb+=sum[u];
 45     
 46     bool t=true;
 47     
 48     for(int i=0;i<h[u].size();i++)
 49     {
 50         int j=h[u][i];
 51         
 52         t&=dfs2(j,col^1,pd,suma,sumb);
 53     }
 54     
 55     return t;
 56 }
 57 
 58 int main()
 59 {
 60     int T;
 61     scanf("%d",&T);
 62     while(T--)
 63     {
 64         int n,m;
 65         int cnt_all=0,cnt=0;
 66         
 67         scanf("%d%d",&n,&m);
 68         
 69         for(int i=1;i<=n;i++) scanf("%d",&a[i]);
 70         
 71         for(int i=1;i<=n;i++)
 72         {
 73             int x;
 74             scanf("%d",&x);
 75             a[i]=x-a[i];
 76             
 77             g[i].clear();//将边初始化 
 78             h[i].clear();
 79         }
 80         
 81         memset(w,0,sizeof(w));
 82         
 83         while(m--)
 84         {
 85             int p,x,y;
 86             
 87             scanf("%d%d%d",&p,&x,&y);
 88             
 89             if(p==1)
 90             {
 91                 w[cnt_all++]={x,y};//存储当t为1时的边 
 92             }
 93             else
 94             {
 95                 g[x].push_back(y),g[y].push_back(x);//t为2时将x与y之间连边 
 96             }
 97         }
 98         
 99         memset(sum,0,sizeof(sum));//联通块总权值初始化 
100         memset(bel,0,sizeof(bel));//每个点属于的联通块 
101         
102         for(int i=1;i<=n;i++)
103         {
104             if(!bel[i]) dfs1(i,++cnt);
105         }
106         
107         memset(book,0,sizeof(book));
108         
109         for(int i=0;i<cnt_all;i++)
110         {
111             int x=bel[w[i].x],y=bel[w[i].y];
112             
113             if(x==y) book[x]=true;//判断自环 
114             else
115             {
116                 h[x].push_back(y);//不是自环则加边 
117                 h[y].push_back(x);
118             }
119         }
120         
121         bool ans=1;
122         
123         memset(color,-1,sizeof(color));//初始化颜色 
124         
125         for(int i=1;i<=cnt;i++)
126         {
127             if(color[i]==-1)//若没被染过色 
128             {
129                 bool pd=false;//判断自环 
130                 LL suma=0,sumb=0;//黑色和白色点的权值总和 
131                 
132                 if(dfs2(i,0,pd,suma,sumb))
133                 {
134                     if(pd) ans&=(suma+sumb)%2==0;//判断联通块权值是否可以变为零 
135                     else ans&=suma==sumb;
136                 }
137                 else
138                 {
139                     ans&=(suma+sumb)%2==0;
140                 }
141             }
142         }
143         
144         if(ans) puts("YES");
145         else puts("NO");
146     }
147 }

 


 

   T2 :

        不会 ~

 

 

 

 


 

 

  T3 : 最小环

      题意:

          给定一个长度为 n 的正整数序列 ai,下标从 1 开始编号。

          我们将该序列视为一个首尾相邻的环。

          更具体地,对于 下标为 i, j(i⩽j) 的两个数 ai​, aj,它们的距离为 min⁡(j−i,i+n−j)。

          现在再给定 m 个整数 k1, k2,..., km。

          对每个 ki​(i=1, 2,..., m),你需要将上面的序列 ai 重新排列,使得环上任意两个距离为 ki 的数字的乘积之和最大。

        大概理解为:给一串数,让你将这串数重新排列,使任意两个中间间隔k-1个数的数的乘积之和最大(数列是头尾相连的)。


 

 

   分析:

          根据样例数据推断出,将最大的数两旁放次大的及第二大的,再将次大的数旁放第三大的,以此类推,最后的结果一定是最大的情况。

          总的来说,每次将未放到环中最大的数放入到环中最大的数旁,可使环的结果最大。

          我们只需要将最大的几个数放入一个环中,再将环中最大的几个数放在一起,总结果就最大了。

          接下来,我们就将当k为0~n/2的情况预处理一下,最后询问时直接输出答案就行。

          然后得算一下距离k与分成多少个环的关系。假设一个环中有些个数,那么 (x*k) mod n=0,所以x=gcd(n,k)。

          (我没想到环的个数是用最大公约数求,只搞了k为一的)

 


 

   代码如下:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 
 7 const int N=1000010;
 8 
 9 long long a[N],f[N];
10 
11 bool com(int a,int b)
12 {
13     return a>b;
14 }
15 
16 int gcd(int a,int b)//求最大公约数 
17 {
18     return b ? gcd(b,a%b) : a;
19 }
20 
21 int main()
22 {
23     int n,m;
24     scanf("%d%d",&n,&m);
25     
26     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
27     
28     sort(a+1,a+n+1,com);//将数从大到小排序   找最大值好用 
29     
30     for(int i=1;i<=n;i++)//处理k为零的情况 
31     {
32         f[0]+=a[i]*a[i];
33     }
34     
35     for(int k=1;k<=n/2;k++)
36     {
37         int t=gcd(n,k),num=n/t;//t为能分成多少个环   num为一个环有多少个数 
38         
39         if(f[t]) continue;
40         
41         for(int i=0;i<=n;i++)//依次枚举最大数放入环中 
42         {
43             if(i%num==0)//如果这是上一个环的最后一个数   就加上下一个环 
44             {
45                 f[t]+=a[i+1]*a[i+2];
46                 continue;
47             }
48             
49             if((i+1)%num==0)//如果这是环的倒数第二个数 
50             {
51                 f[t]+=a[i]*a[i+1];
52             }
53             else f[t]+=a[i]*a[i+2];//正常情况下 
54         }
55         
56         
57     }
58     while(m--)
59     {
60         int k;
61         scanf("%d",&k);
62         
63         if(!k) printf("%lld\n",f[0]);//特判当k为零的情况 
64         else
65         {
66             k=gcd(n,k);
67             printf("%lld\n",f[k]);
68         }
69     }
70 }

 

posted on 2020-03-07 21:15  ArrogHie  阅读(678)  评论(0)    收藏  举报