AtCoder Grand Contest 012 题解

来源:互联网 发布:winrar软件下载免费版 编辑:程序博客网 时间:2024/06/06 01:48

A:

有3N个选手参加一场比赛,第i个人的力量值为ai,他们将每三个人分为一组,每组的力量值为三个人力量值的中位数。问这样分组后得到的N组里面,参赛队伍力量值的和的最大值是多少? N <= 10^5


solution:

考虑贪心解法,先将所有选手按照力量值升序排好,容易观察到最优的分法是让第n - 1,n - 3,n - 5...这些选手的力量值产生贡献。证明也很简单,如果只有三个人,显然成立。当人数大于3,第一个选手力量值最小,无法产生贡献,最后一个选手力量值太大,无法产生贡献,让他们分到一组,再加上第n - 1个人,这样肯定是很优的,于是就变成了3N - 3个人的子问题,证明完毕

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 3E5 + 30;typedef long long LL;int n,A[maxn];LL Ans;int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n;for (int i = 1; i <= 3 * n; i++) scanf("%d",&A[i]);sort(A + 1,A + 3 * n + 1);for (int i = 0; i < n; i++) Ans += 1LL * A[3 * n - 1 - 2 * i];cout << Ans << endl;return 0;}


B:

现在有一个n个点m条边的无向图,需要执行q次操作,每次操作是将距离点x不超过d的所有点都染成某种颜色,需要输出所有操作结束后每个点的颜色。n <= 10^5,0 <= d <= 10


solution:

注意到d很小,考虑设计一个和d相关的算法。将每个点拆成11个,每个拆出来的点储存从这个点出发,还需要传播k步的那种颜色。先将修改离线,因为后面的颜色会覆盖先前的颜色,所以当两种需要传播的颜色相遇时只保留较迟出现的那个就行了。按照修改的距离从大到小处理,每次把整张图遍历一遍,并把需要传播的颜色标记放到下一层即可

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 1E5 + 10;struct Mark{int col,ti; Mark(){}Mark(int col,int ti): col(col),ti(ti){}bool operator > (const Mark &B) const {return ti > B.ti;}}last[11][maxn],Ans[maxn];int n,m,q;vector <int> v[maxn];inline Mark max(const Mark &x,const Mark &y) {return x > y ? x : y;}int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n >> m;while (m--){int x,y; scanf("%d%d",&x,&y);v[x].push_back(y); v[y].push_back(x);}cin >> q;for (int i = 1; i <= q; i++){int x,d,c;scanf("%d%d%d",&x,&d,&c);Mark now = Mark(c,i);Ans[x] = max(Ans[x],now);last[d][x] = max(last[d][x],now);}for (int j = 10; j; j--)for (int i = 1; i <= n; i++){if (!last[j][i].ti) continue;for (int k = 0; k < v[i].size(); k++){int to = v[i][k];Ans[to] = max(Ans[to],last[j][i]);last[j - 1][to] = max(last[j - 1][to],last[j][i]);}}for (int i = 1; i <= n; i++) printf("%d\n",Ans[i].col);return 0;}

-----------分割线-----------------

由于是第一次打AGC以及苟蒻实在是naive....后面的题都是赛后抄题解写的啦。。。


C:

我们定义一个字符串s是"good",当它能被拆分成xx的形式,也就是它是由一个字符串x复制一遍直接接在后面形成的。现在需要构建一个字符串s,使得它的所有2^|s|个子序列中,有且仅有N个子序列构成的字符串是"good"。|s|中的字符使用1~100的数字代替,1 <= |s| <= 200,1 <= N <= 10^12,可以证明总是有解的


solution:

考虑一个{1~n}的排列后面紧接着数字1~n,这样的一个串中"good"子序列的数量。因为"good"串是copy以后产生的,所以每种字符前半段出现后半段也要出现,而上面构造的那种串每种字符最多出现两次,所以当一个"good"被拆分成xx后,肯定前半段一个x后半段一个x,因此"good"子序列的数量就等于前半段上升子序列的数量了。

定义空串也属于"good"子序列,那么构造一个拥有N + 1个"good"子序列的东西就行了

对于一个{1~k}的排列{p1,p2,p3,...,pk},记当前拥有的"good"子序列数量为x

那么排列{p1,p2,p3,...,pk,k + 1}拥有的"good"子序列显然是x * 2

排列{k + 1,p1,p2,p3,...,pk}拥有的"good"子序列显然是x + 1

综上所述,一个简单的递归程序就能解决这个问题了,排列的大小n不超过2logn = 80,因此总长不超过160

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;typedef long long LL;int cnt;LL n;deque <int> Q;void Build(LL N){if (N == 1) return;if (N & 1){Build(N - 1);Q.push_front(++cnt);}else{Build(N >> 1LL);Q.push_back(++cnt);}}int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n; Build(n + 1); cout << (cnt << 1) << endl;while (!Q.empty()) printf("%d ",Q.front()),Q.pop_front();for (int i = 1; i < cnt; i++) printf("%d ",i); cout << cnt << endl;return 0;}


D:

现在有N个球排成一行,第i个球的颜色为ci,重量为wi,给定参数X,Y,可以执行以下两种操作:1.选择两个颜色相同的球,如果它们的重量和不超过X,交换他们的位置。2.选择两个颜色不同的球,如果它们的重量和不超过Y,交换它们的位置。问一共可以构造出多少种不同的颜色序列?两种序列不同当且仅当某一位置上的颜色不同。1 <= n <= 2*10^5


solution:

球与球之间交换的可行性显然是满足传递性的,也就是说,有三个球a,b,c,如果a和b能交换,b和c能交换,通过(a,b)(b,c)(a,b)三次操作,就能实现b不动而交换a,c。那么就有一种简单的思路,暴力枚举每对球,如果能交换就在它们之间连一条边,这样就构成了一些连通块。因为连通块之间独立,所以对每个连通块,用组合数统计一下答案,最后乘起来就行了。

这样建图边数是O(N^2)的,显然过不去。

对于同一种颜色,拿出最轻的那个球记为a,如果这种颜色的两个球b,c能交换,那么它们肯定都能和a交换,于是对于同一种颜色的球,只需要考虑它们和最轻的那个球的关系就行了

对于颜色不同的情况,每种球取出最轻的那个,将所有取出的球排序,记最轻的两个分别为a1,a2,类似的,只要考虑a1,a2和其它颜色的球连的边就行了。对于两个不同颜色的球b,c,假设有边相连

如果b != a1 && c != a1 那么肯定有b - a1 - c

如果b != a2 && c != a2 那么肯定有b - a2 - c

否则假设 b != a1 && c != a2 那么肯定有b - a1 - a2 - c

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 2E5 + 20;typedef long long LL;const LL mo = 1000000007;int n,X,Y,tp,Clock,tot,Ans = 1,cnt[maxn],last[maxn],c[maxn],w[maxn],stk[maxn],Fac[maxn],Inv[maxn];bool vis[maxn];queue <int> Q; stack <int> s;vector <int> v[maxn],g[maxn];inline int Mul(const LL &x,const LL &y) {return x * y % mo;}inline bool cmpw(const int &x,const int &y) {return w[x] < w[y];}inline int C(const int &N,const int &M) {return Mul(Fac[N],Mul(Inv[M],Inv[N - M]));}int ksm(int x,int y){int ret = 1;for (; y; y >>= 1){if (y & 1) ret = Mul(ret,x);x = Mul(x,x);}return ret;}void BFS(int st){last[c[st]] = Clock; s.push(c[st]);vis[st] = cnt[c[st]] = 1; Q.push(st);while (!Q.empty()){int k = Q.front(); Q.pop(); ++tot;for (int i = 0; i < v[k].size(); i++){int to = v[k][i]; if (vis[to]) continue;if (last[c[to]] == Clock) ++cnt[c[to]];else cnt[c[to]] = 1,last[c[to]] = Clock,s.push(c[to]);vis[to] = 1; Q.push(to);}}}void Add_Edgs(int st){for (int i = 1; i <= n; i++){if (c[i] == c[st]) continue;if (w[i] + w[st] > Y) continue;v[st].push_back(i); v[i].push_back(st);}}int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n >> X >> Y; Fac[0] = 1;for (int i = 1; i <= n; i++)scanf("%d%d",&c[i],&w[i]),g[c[i]].push_back(i);for (int i = 1; i <= n; i++) Fac[i] = Mul(Fac[i - 1],i);Inv[n] = ksm(Fac[n],mo - 2); for (int i = n - 1; i >= 0; i--) Inv[i] = Mul(Inv[i + 1],i + 1);for (int i = 1; i <= n; i++){if (!g[i].size()) continue;sort(g[i].begin(),g[i].end(),cmpw);stk[++tp] = g[i][0];for (int j = 1; j < g[i].size(); j++){if (w[g[i][0]] + w[g[i][j]] > X) break;v[g[i][0]].push_back(g[i][j]);v[g[i][j]].push_back(g[i][0]);}}sort(stk + 1,stk + tp + 1,cmpw);Add_Edgs(stk[1]); if (stk[2]) Add_Edgs(stk[2]);for (int i = 1; i <= n; i++){if (vis[i]) continue; ++Clock; BFS(i);while (!s.empty()){int k = s.top(); s.pop();Ans = Mul(Ans,C(tot,cnt[k])); tot -= cnt[k];}}cout << Ans << endl;return 0;}


E:

有n个绿洲排成一行,第i个绿洲的坐标为xi,有一只骆驼在这里旅行,他想遍历所有绿洲。它的驼峰初始体积为v并装有v的水。骆驼有两种移动方式:1.从位置xi走到位置xj并消耗abs(xi - xj)的水 2.如果此刻骆驼的驼峰剩余的水体积为v,v > 0,那么他可以选择一个坐标,瞬间移动过去,移动结束后驼峰的水量清空并且体积变为[v/2](向下取整)。当骆驼在一块绿洲上时,它可以无限补充自己的水量(显然不能超过驼峰体积),现在骆驼想知道,对于每一块绿洲,如果它从这里出发,是否能够遍历所有的绿洲。注意每块绿洲允许经过无限次,任何时刻骆驼驼峰内的水量不能为负数。2 <= n,v <= 2*10^5


solution:

因为操作2会减少驼峰体积,所以使用次数不超过logv。考虑选择一个起点出发,肯定是先向左向右尽量探索能到达的绿洲,然后再考虑使用瞬移。当两个绿洲之间的距离不超过驼峰的体积时显然是能够到达的,到达之后再补充水分即可。

预处理数组L[i][j]为从点i出发,已经跳过了j次,往左最远能走到哪个绿洲,类似地处理好R[i][j]。那么从起点i出发,记l = L[i][0],r = R[i][0],如果当前能够探索完所有绿洲,则一定存在一种将剩余次数分成两个不相交集合的方案,使得用其中一个集合能遍历1 ~ l - 1,另一个能遍历r + 1 ~ n

用预处理好的L,R数组做个dp就行了

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int N = 20;const int maxn = 2E5 + 20;int n,v,Max,p[maxn],Min[maxn],Left[maxn][N],Right[maxn][N],f[1 << N],g[1 << N];void Pre_Work(int k,int now){Left[1][k] = 1; Right[n][k] = n;for (int i = 2; i <= n; i++)Left[i][k] = p[i] - p[i - 1] <= now ? Left[i - 1][k] : i;for (int i = n - 1; i; i--)Right[i][k] = p[i + 1] - p[i] <= now ? Right[i + 1][k] : i;}int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n >> v;for (int i = 1; i <= n; i++) scanf("%d",&p[i]);for (int x = v; ; x >>= 1){Pre_Work(Max++,x);if (!x) break;}for (int i = 0; i <= n; i++) Min[i] = maxn; g[0] = n + 1;for (int o = 1; o < (1 << Max); o++){g[o] = n + 1;for (int i = 0; i < Max; i++){if (!(o & (1 << i))) continue;int op = o ^ (1 << i);f[o] = f[op] == n ? n : max(f[o],Right[f[op] + 1][i]);g[o] = g[op] == 1 ? 1 : min(g[o],Left[g[op] - 1][i]);}}for (int o = 0; o < (1 << Max); o++){int A = o,B = ((1 << Max) - 1) ^ o;if (A & 1) A ^= 1; if (B & 1) B ^= 1;Min[f[A]] = min(Min[f[A]],g[B]);}for (int i = n - 1; i >= 0; i--) Min[i] = min(Min[i],Min[i + 1]);for (int i = 1; i <= n; i++)puts(Min[Left[i][0] - 1] <= Right[i][0] + 1 ? "Possible" : "Impossible");return 0;}


F:

你有一个长度为2N - 1的数列a,第i个位置的数字是ai,现在,你可以把a中的数字随意排列,然后用这个新的数列a生成一个长度为N的数列b,具体的,bi = {a1,a2,a3,...,a2i-1}的中位数

询问一共有多少种可能的数列b。N <= 50,答案对10^9 + 7取模


solution:


#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std; const int N = 55;const int M = 155;typedef long long LL;const LL mo = 1000000007; int n,m,Ans,A[M],d[N],f[N][M][M];bool Min[N],Max[N]; void Add(int &x,const int &y) {x += y; if (x > mo) x -= mo;} int main(){#ifdef DMCfreopen("DMC.txt","r",stdin);#endifcin >> n; m = 2 * n - 1;for (int i = 1; i <= m; i++) scanf("%d",&A[i]);sort(A + 1,A + m + 1); int mid = 1 + m >> 1;for (int i = 2; i <= n; i++){if (A[mid - i + 1] != A[mid - i + 2]) ++d[i],Min[i] = 1;if (A[mid + i - 1] != A[mid + i - 2]) ++d[i],Max[i] = 1;}f[1][1][1] = 1;for (int i = 1; i < n; i++)for (int j = 1; j <= m; j++)for (int k = 1; k <= j; k++){if (!f[i][j][k]) continue;int tot = j + d[i + 1];int pos = Min[i + 1] ? k + 1 : k;for (int l = 1; l < pos; l++)Add(f[i + 1][tot - (pos - l - 1)][l],f[i][j][k]);Add(f[i + 1][tot][pos],f[i][j][k]);for (int l = pos + 1; l <= tot; l++)Add(f[i + 1][tot - (l - pos - 1)][pos + 1],f[i][j][k]);}for (int i = 1; i <= m; i++)for (int j = 1; j <= i; j++)Add(Ans,f[n][i][j]);cout << Ans << endl;return 0;}


0 0
原创粉丝点击