Codeforces #447 Div.2 Tutorial

Problem A:QAQ

给一个字符串,求出可非连续的QAQ序列有多少个。

Analysis:比较水的一道题,记录每一个Q的位置,预处理A的个数即可

              然而还是fst了,原因是未考虑一个Q都没有的极端情况,导致vector().size-1溢出!

              以后一定要注意,当对unsigned int类型的vector().size作减法时考虑是否可能出现负数情况

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 int cnt[105];
 5 string s; 
 6 vector<int> q;
 7 
 8 int main()
 9 {
10     cin >> s;
11     for(int i=0;i<s.size();i++)
12     {
13         cnt[i+1]=cnt[i];
14         if(s[i]=='A') cnt[i+1]++;
15         else if(s[i]=='Q') q.push_back(i+1);
16     }
17     
18     if(!q.size()) cout << 0,return 0; //对极端情况要特殊考虑 
19                           //因为vector.size()是unsigned int,减1时会溢出 
20     int res=0;
21     for(int i=0;i<q.size()-1;i++)
22         for(int j=i+1;j<q.size();j++)
23         {
24             int x=q[i],y=q[j];
25             res+=(cnt[y]-cnt[x]);
26         }
27     cout << res;
28     
29     return 0;
30 }
Problem A

 Problem B:Ralph and his magic field

喜闻乐见的组合数学题,可划归为:在一个n*m的表格上填0和1,问在每行每列的和均为奇数或偶数时总方案数为多少

Analysis:一道比较考思维的题目,而思维能力不足一直我的弱项

              其实看到n,m为1e18就知道是结论题,这时候推不出来暴力打表找规律也行啊,以后还是要灵活一些

             正解就是把问题转换成(n-1)*(m-1)的全排列问题,最后的一行一列根据最终要求推出来就行了

             但要注意的是,当要求为奇数,且n、m奇偶性不同时,方案数为0

            (证明:1、反证法:只看横行为偶数个奇数,只看竖行为奇数个奇数,但总和不应有变,矛盾

                          2、利用(n-1)*(m-1)的矩形来证明:在(n-1)*(m-1)表上均为1时,最后一行和最后一列奇偶性必然不同,而每将1个1变为0,两边的奇偶性都不会变为相同)

             

           至于求2^(n-1)^(m-1)就没什么好讲的了,只要注意快速幂必须分两次求,否则(n-1)*(m-1)会爆long long

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 const int MOD=1000000007;
 5 
 6 long long n,m;
 7 int k;
 8 
 9 long long quick_pow(long long a,long long b)
10 {
11     long long base=a,res=1;
12     while(b)
13     {
14         if(b%2) res=res*base%MOD;
15         b>>=1;
16         base=base*base%MOD;
17     }
18     
19     return res;
20 }
21 
22 int main()
23 {
24     cin >> n >> m >> k;
25     
26     if(k==-1 && (n%2!=m%2)) cout << 0;
27     else cout << quick_pow(quick_pow(2,n-1),m-1);
28     
29     return 0;
30 }
Problem B

Problem C:Marco and GCD Sequence

这次中国选手出题就变成数学大赛了啊(雾

给定有序集合S,S是由一个n个元素组成的序列中对于每个1 ≤ i ≤ j ≤ n计算出的gcd(ai, ai + 1, ..., aj)产生的,输出你构造的序列,如没有输出-1

Analysis:又是一道思维题

              我当时的想法是既然序列中的数肯定都属于S,且S中的最后一个数必选,那从后往前贪心选取不就行了。当时写起来便很虚,其实明显是有bug的,因为没有想到普遍构造

              

              对于这类构造题,首先可以想一想如果将条件变强,是否存在一种通解

              对于本题而言,便猜想如果s1,s2,s3.......sn均为s1,即gcd(a1,a2....an)的倍数时,便可将序列构造为s1,s2,s1,s3,s1,s4,s1,s5.....s1,sn

              那如果s1不是s1,s2,s3....sn的公约数呢?那便可证明不存在这样的序列,因为a1,a2....an的最大公约数便是s1。

              对于此题,我只想到S中的最后一个数必然为an,却未想到S1必然是a1,a2,a3.....an的最大公约数

              对这类序列区间求值后统一排序的题目首尾均是关键

              其次,在发现线性算法无法很好解决时,考虑对特定情况下的构造,再进行推广

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 int m,dat[1005];
 5 
 6 int gcd(int a,int b)
 7 {
 8     if(a%b==0) return b;
 9     else return gcd(b,a%b);
10 }
11 
12 int main()
13 {
14     cin >> m;
15     for(int i=1;i<=m;i++) cin >> dat[i];
16     
17     if(m==1)
18     {
19         cout << 1 << endl << dat[1];
20         return 0;
21     }
22     
23     int all=dat[1];
24     for(int i=2;i<=m;i++) all=gcd(all,dat[i]);
25     
26     if(all!=dat[1]) cout << -1;
27     else
28     {
29         cout << 2*(m-1) << endl;
30         for(int i=2;i<=m;i++) cout << dat[1] << " " << dat[i] << " ";
31     }
32     
33     return 0;
34 }
Problem C

Problem D:Ralph And His Tour in Binary Country

给定一棵树,每次给1个点A,求到A的距离不超过H的点到A的距离和

Analysis:这道题一开始以为要树剖,结果发现并不用

              在每一颗子树下,可以预处理出子树下所有点到子树根节点的距离及前缀和,从而O(logn)求出该子树下到子树根节点距离不超过H-x的和

              因此,我们可以从A节点开始,一层层向上遍历,找父亲,直到距离已超过H,每次求出当前子树下到子树根节点B距离不超过H-x(A到B的距离)的和

              但这样明显会有一定的重复计算,因此每次我们找到父亲后,只能查找该父亲下的另一棵子树

              这时实现时有一些小技巧:1、当数据在节点时可以递归建树时。但当数据在边上时,应当以边为线索进行建树

                                                         2、树上的遍历为保证不重复,只要每次记录上一次操作的last节点,保证不重复计算

                                                         3、找到边与点的关系,在本题中,第i个点到其父节点的边为Ei-1

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 typedef long long ll;
 5 const int MAXN=1000005;
 6 
 7 int n,m,len[MAXN],A,H,t[MAXN];
 8 vector<int> a[MAXN];
 9 vector<ll> pre[MAXN];
10 
11 inline int read() //OI优化标准模块,专防卡常 
12 {
13     char ch;int num,f=0;
14     while(!isdigit(ch=getchar())) f|=(ch=='-');
15     num=ch-'0';
16     while(isdigit(ch=getchar())) num=num*10+ch-'0';
17     return f?-num:num;
18 }
19 
20 inline void write(ll x)
21 {
22     if(x<0) putchar('-'),x=-x;
23     if(x>9) write(x/10);
24     putchar(x%10+'0');
25 }
26 
27 void merge(int x,int y)  //类似于归并排序的操作 
28 {    
29     int lx=a[x].size(),ly=a[y].size(),d=len[y-1];
30     int k=0,p=0,q=0;
31     
32     while(p<lx && q<ly)
33     {
34         if(a[x][p]<a[y][q]+d) t[k++]=a[x][p],p++;
35         else t[k++]=a[y][q]+d,q++;
36     }
37     
38     while(p<lx) t[k++]=a[x][p],p++;
39     while(q<ly) t[k++]=a[y][q]+d,q++;
40     
41     a[x].clear();
42     for(int i=0;i<k;i++) a[x].push_back(t[i]);
43 }
44 
45 ll cal(int node,ll tar)
46 {
47     ll ret=0;
48     ll pos=lower_bound(a[node].begin(),a[node].end(),tar)-a[node].begin()-1;
49     ret=tar*(pos+1)-pre[node][pos];
50     
51     return ret;
52 }
53 
54 ll BinaryCount(int node)
55 {
56     ll res=0,t=H,last=node; //last保证不重复计算 
57     res+=cal(node,t);t-=len[node-1];node>>=1; //先加上当前节点 
58     while(node>0 && t>0)
59     {
60         res+=t; //加上当前的根节点 
61         int lch=(node<<1),rch=((node<<1)|1); //处理当前根节点下的子节点 
62         if(lch<=n && lch!=last && t-len[lch-1]>0) res+=cal(lch,t-len[lch-1]);
63         if(rch<=n && rch!=last && t-len[rch-1]>0) res+=cal(rch,t-len[rch-1]);
64         
65         last=node;t-=len[node-1];node>>=1;
66     }
67     return res;
68 }
69 
70 int main()
71 {
72     cin >> n >> m;
73     for(int i=1;i<=n-1;i++) len[i]=read();
74     for(int i=1;i<=n;i++) a[i].push_back(0);
75     
76     for(int i=n;i>1;i--) merge(i/2,i);  //以边为线索进行更新 
77     
78     for(int i=1;i<=n;i++) //对前缀和的预处理 
79         for(int j=0;j<a[i].size();j++)
80             if(!j) pre[i].push_back(a[i][j]);
81             else pre[i].push_back(pre[i][j-1]+a[i][j]);
82     
83     for(int i=1;i<=m;i++)
84     {
85         cin >> A >> H;
86         write(BinaryCount(A));cout << endl;
87     }
88     
89     return 0;
90 }
Problem D

Problem E:Ralph and Mushrooms

Analysis:莫名其妙,E题成了道简单题

              主要就是先Tarjan缩点,在每一个强联通分量里对数据进行特殊计算

              接下来在DAG上求最长路

              注意,此题的起点不一定入度为零,因此不能直接拓扑排序,应记忆化搜索

              Tips:1、对(n-1)+(n-2)+(n-3)+(n-4)......(n-k)的计算是一个难点,标算的数学计算法还没看懂......

                      2、对于DAG的处理:不一定非要拓扑+DP,可以直接记忆化搜索,但要注意的是此时维护的是到终止节点的最大距离

                      3、Tarjan缩点后重新建图时,可以不用处理两个节点间的多条边

  1 #include <bits/stdc++.h>
  2 
  3 using namespace std;
  4 typedef pair<int,int> P;
  5 
  6 const int N=1000005;
  7 int n,m,st,low[N],dfn[N],time_point=0,col[N],cnt=0;
  8 bool vis[N],instack[N];
  9 long long res=0,tot[N],dp[N];
 10 
 11 inline int read()
 12 {
 13     char ch;
 14     while(!isdigit(ch=getchar()));
 15     int num=ch-'0';
 16     while(isdigit(ch=getchar())) num=num*10+ch-'0';
 17     return num;
 18 }
 19 
 20 struct ed
 21 {
 22     int x,y,w;
 23 }edge[N];
 24 
 25 vector<P> a[N];
 26 stack<int> s;
 27 
 28 void tarjan(int cur)  //Tarjan模板 
 29 {
 30     time_point++;
 31     dfn[cur]=low[cur]=time_point;
 32     vis[cur]=true;
 33     instack[cur]=true;
 34     s.push(cur);
 35     
 36     for(int i=0;i<a[cur].size();i++)
 37     {
 38         int u=a[cur][i].first;
 39         if(!vis[u])
 40         {
 41             tarjan(u);
 42             low[cur]=min(low[cur],low[u]);
 43         }
 44         else if(instack[u])
 45         {
 46             low[cur]=min(low[cur],low[u]);
 47         }
 48     }
 49     
 50     if(dfn[cur]==low[cur])
 51     {
 52         cnt++;int t;
 53         do
 54         {
 55             t=s.top();s.pop();
 56             instack[t]=false;
 57             col[t]=cnt;
 58         }while(t!=cur);
 59     }
 60 }
 61 
 62 long long cal(int t)  //标算的数学计算法 
 63 {
 64     long long k=(long long)(sqrtl((long double)t*2+0.25)-0.5);
 65     return (k*(6*t-(k+1)*(k+2)))/6+t;
 66 }
 67 
 68 void dfs(int node) //记忆化 
 69 {
 70     if(dp[node]) return;
 71     for(int i=0;i<a[node].size();i++)
 72     {
 73         int v=a[node][i].first;
 74         dfs(v);
 75         dp[node]=max(dp[node],dp[v]+a[node][i].second);  //维护到终止点的值 
 76     }
 77     dp[node]+=tot[node];  //在完结后才加上当前节点的值 
 78 }
 79 
 80 int main()
 81 {
 82     n=read();m=read();
 83     for(int i=1;i<=m;i++)
 84     {
 85         edge[i].x=read();edge[i].y=read();edge[i].w=read();
 86         a[edge[i].x].push_back(P(edge[i].y,edge[i].w));
 87     } 
 88     st=read();
 89     
 90     tarjan(st);
 91     
 92     for(int i=0;i<N;i++) a[i].clear();
 93     
 94     for(int i=1;i<=m;i++)  //对缩点后每一个点的数据更新 
 95     {
 96         int u=edge[i].x,v=edge[i].y;
 97         if(col[u]==col[v])
 98         {
 99             tot[col[u]]+=cal(edge[i].w);
100         }
101     }
102     for(int i=1;i<=m;i++)
103     {
104         int u=edge[i].x,v=edge[i].y;  //这里可以不用考虑两个点间有多条边的情况 
105         if(col[u]!=col[v])
106         {
107             a[col[u]].push_back(P(col[v],edge[i].w));
108         }        
109     }
110     
111     dfs(col[st]);
112     
113     cout << dp[col[st]];
114     
115     return 0;
116 }
Problem E
原文地址:https://www.cnblogs.com/newera/p/7900570.html