Codeforces #447 Div.2 Tutorial
Problem A:QAQ
给一个字符串,求出可非连续的QAQ序列有多少个。
Analysis:比较水的一道题,记录每一个Q的位置,预处理A的个数即可
然而还是fst了,原因是未考虑一个Q都没有的极端情况,导致vector().size-1溢出!
以后一定要注意,当对unsigned int类型的vector().size作减法时考虑是否可能出现负数情况
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 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的最大公约数
对这类序列区间求值后统一排序的题目首尾均是关键
其次,在发现线性算法无法很好解决时,考虑对特定情况下的构造,再进行推广
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 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缩点后重新建图时,可以不用处理两个节点间的多条边
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }