AtCoder Grand Contest 013 题解
来源:互联网 发布:tc编程怎么收费 编辑:程序博客网 时间:2024/05/21 17:18
A:
给出一个长度为
N 的数组A
需要将A 划分为若干个连续的子串
使得每个子串要么单调不增要么单调不降
问最少分成多少个
1≤N≤105,1≤Ai≤109
solution A:
贪心。。。显然是贪心。。。
因为一个数被划分到前面并不影响后面的决策
所以每次尽量长地划分就行了
#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;int n,Ans,A[maxn];bool vis[maxn];void Check(int k){ int a,b; a = b = 1; for (int i = k + 1; i <= n; i++) { if (A[i] > A[i - 1]) {a = i - k; break;} if (i == n) a = n + 1 - k; } for (int i = k + 1; i <= n; i++) { if (A[i] < A[i - 1]) {b = i - k; break;} if (i == n) b = n + 1 - k; } int c = max(a,b); for (int i = 0; i < c; i++) vis[k + i] = 1;}int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> n; for (int i = 1; i <= n; i++) scanf("%d",&A[i]); for (int i = 1; i <= n; i++) if (!vis[i]) Check(i),++Ans; cout << Ans << endl; return 0;}
B:
给出一张
N 个点M 条边保证连通的简单无向图(无重边无自环)
请找出任意一条符合以下条件的路径:
- 至少包含两个点
- 经过每个点恰好一次
- 所有与路径端点有边直接相连的点都必须在路径里
可以证明一定存在合法解
2≤N≤105,1≤M≤105
solution B:
做法挺傻逼的,就是卡了有点久。。
从1 号点出发随机dfs 出一条路径
将沿途所有点都标记,规定如果一个点被标记了,那么再也不能经过
这样,一定会走到一个点S ,使得再也找不到后继点了
将之前这条路径经过的点存在一个栈里,将栈内元素翻转
这样变成了一条S 到1 的路径,
并且所有与S 有边直接相连的点都在路径里
同理,再从1 出发任意找到一条路径就好了
两条路径拼起来显然合法
#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;int n,m,tp,stk[maxn];bool vis[maxn];vector <int> v[maxn];void Dfs(int x){ for (int i = 0; i < v[x].size(); i++) { int to = v[x][i]; if (vis[to]) continue; vis[to] = 1; stk[++tp] = to; Dfs(to); return; }}int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> n >> m; while (m--) { int x,y; scanf("%d%d",&x,&y); v[x].push_back(y); v[y].push_back(x); } stk[tp = 1] = 1; vis[1] = 1; Dfs(1); reverse(stk + 1,stk + tp + 1); Dfs(1); cout << tp << endl; for (int i = 1; i < tp; i++) printf("%d ",stk[i]); cout << stk[tp] << endl; return 0;}
C:
将一个周长为
L 的圆用L 个点均分成L 段
这L 个点按照顺时针标号为0→L−1
在这L 个点上分布着N 只蚂蚁,第i 只蚂蚁此时在点xi 上
每只蚂蚁初始时都朝着顺时针或逆时针方向
每一秒钟,每只蚂蚁都向自己面对的方向移动1 的距离
如果有两只蚂蚁在某一时刻碰到对方,那么它们瞬间改变自己的朝向
请回答过了T 秒后每只蚂蚁的坐标
1≤N≤105,1≤L,T≤109,0≤xi<L,∀i<j,xi<xj
solution C:
关于蚂蚁的问题有一个通用的套路。。
两只相撞的蚂蚁不妨将它们视为穿过对方
可以发现,这样转换后最终每只蚂蚁出现的位置的集合是不变的
那么根据每只蚂蚁初始的朝向和总的时间
就能确定出T 秒以后哪些位置上会有蚂蚁存在
因为蚂蚁们相撞后会掉头,所以它们的相对位置始终不变
因此,只需要确定第一只蚂蚁最后的坐标,其它蚂蚁的坐标也就确定了
一开始,第一只蚂蚁的坐标是所有蚂蚁中最小的
每当有蚂蚁从L−1 顺时针走到0 ,一号蚂蚁的坐标就向后一位
每当有蚂蚁从0 逆时针走到L−1 ,一号蚂蚁的坐标就向前一位
算一下以上两种情况发生的次数就行了
#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;int N,L,T,d,A[maxn];int pre(int x) {return x == 1 ? N : x - 1;}int nex(int x) {return x == N ? 1 : x + 1;}int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> N >> L >> T; for (int i = 1; i <= N; i++) { int x,w; scanf("%d%d",&x,&w); if (w == 1) { int dt = T; if (L - x <= dt) dt -= (L - x),++d,x = 0; x += dt; d += dt / L; x %= L; } else { int dt = T; if (x + 1 <= dt) dt -= (x + 1),--d,x = L - 1; x -= dt; d -= dt / L; x %= L; if (x < 0) x += L; } A[i] = x; d %= N; } int pos = 1; d %= N; while (d < 0) pos = pre(pos),++d; while (d > 0) pos = nex(pos),--d; sort(A + 1,A + N + 1); for (int i = pos; i <= N; i++) printf("%d\n",A[i]); for (int i = 1; i < pos; i++) printf("%d\n",A[i]); return 0;}
D:
你有一个装着红色或蓝色砖块的盒子
初始的时候红色和蓝色砖块共有N 块,数量随机
需要连续做下面的操作M 轮:
- 从盒子里随机拿一个砖块
- 往盒子里放入一个红色砖块和一个蓝色砖块
- 从盒子里随机拿一个砖块
将每次拿出的砖块摆成一行,这样就得到了一个长度为
2M 的颜色序列
询问不同的颜色序列的个数,答案对109+7 取模
1≤N,M≤3000
solution D:
以下为了叙述方便,将红砖块简记为
R ,蓝砖块简记为B
本质不同的操作只有四种:
RR ,需要此时至少有一个R ,完成后R−1,B+1 RB ,需要此时至少有一个R ,完成后不发生改变BB ,需要此时至少有一个B ,完成后R+1,B−1 BR ,需要此时至少有一个B ,完成后不发生改变这就引向一个简单的
dp
记f[i][j] 为执行了i 次操作后,盒子里还剩余j 个R 的不同序列数
但是显然,不同的初始状态可能指向同一个颜色序列
所以上面这个dp 方程会算重
定义一次操作为特殊的,当操作结束后或者操作过程中R 的数量达到最小
那么定义三维状态f[i][j][k] 指向是否已经有过特殊操作
显然,任意一种颜色序列有且仅有一种初始砖块分布
使得每次R 取到最小值时其值都为0
因此O(NM) 的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 = 3003;typedef long long LL;const LL mo = 1000000007;int n,m,Ans;LL f[N][N][2];int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> n >> m; f[0][0][1] = 1; if (n == 1) { Ans = 2; for (int i = 1; i <= m; i++) Ans = Ans * 2 % mo; cout << Ans << endl; return 0; } for (int i = 1; i <= n; i++) f[0][i][0] = 1; for (int i = 1; i <= m; i++) { f[i][0][1] = (f[i - 1][0][1] + f[i - 1][1][0] + f[i - 1][1][1]) % mo; f[i][1][0] = (f[i - 1][1][0] + f[i - 1][2][0]) % mo; f[i][1][1] = (f[i - 1][0][1] + f[i - 1][1][0] + 2LL * f[i - 1][1][1] + f[i - 1][2][1]) % mo; f[i][n][0] = (f[i - 1][n - 1][0] + f[i - 1][n][0]) % mo; f[i][n][1] = (f[i - 1][n - 1][1] + f[i - 1][n][1]) % mo; for (int j = 2; j < n; j++) { f[i][j][0] = (f[i - 1][j - 1][0] + 2LL * f[i - 1][j][0] + f[i - 1][j + 1][0]) % mo; f[i][j][1] = (f[i - 1][j - 1][1] + 2LL * f[i - 1][j][1] + f[i - 1][j + 1][1]) % mo; } } for (int i = 0; i <= n; i++) Ans += f[m][i][1],Ans %= mo; cout << Ans << endl; return 0;}
E:
现在有一根长度为
n 的木棍
木棍上有m 个标记,第i 个标记距离左端点为xi
需要在这根木棍上摆放一些正方形,满足:
- 每一个正方形的边长都是正整数
- 每一个正方形都要有一条边紧贴着木棍
- 木棍必须被完全覆盖,正方形的边界也不能超出木棍
- 两个相邻正方形的交界处不能有标记
定义每种方案的美丽度为所有正方形面积的乘积
求所有合法方案的美丽度的和,答案对109+7 取模
1≤N≤109,0≤M≤105,1≤x1<x2<…<xm≤N−1
solution E:
将原问题的模型稍微修改一下
现在有n 个连续的空格,其中左边界和右边界必须放置隔板
可以在两个相邻空格摆放隔板,可以在空格内放红球和蓝球
需要统计,有多少种不同的方案,满足:
- 每相邻的两块隔板之间都恰好有一个红球和一个蓝球
- 隔板不能摆放在有标号的地方
两种方案不同,当隔板的摆放不同或隔板间红蓝球分布不同
隔板的摆放对应正方形的划分,红蓝球的摆放对应正方形的面积
因此总方案数对应着美丽度之和
那么定义f[i][j] 为考虑前i 个空格,
从最后一块隔板到现在已经摆放了j 个球的方案数
不同的空格只有三类,每类可以写出固定的转移方程
第二维状态只有3 种,因此可以使用矩阵乘法优化转移
O(33Mlog2N)
#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 = 3;const int maxn = 1E5 + 10;typedef long long LL;const LL mo = 1000000007;inline int Mul(const LL &x,const LL &y) {return x * y % mo;}inline int Add(const int &x,const int &y) {return x + y < mo ? x + y : x + y - mo;}struct data{ int a[N][N]; data(){memset(a,0,sizeof(a));} data operator * (const data &B) { data c; for (int k = 0; k < N; k++) for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) c.a[i][j] = Add(c.a[i][j],Mul(a[i][k],B.a[k][j])); return c; }}A,B,C,D;int n,m,X[maxn];void Pre_Work(){ for (int i = 0; i < N; i++) A.a[i][i] = 1; B.a[0][0] = 2; B.a[0][1] = 1; B.a[0][2] = 1; B.a[1][0] = 2; B.a[1][1] = 1; B.a[1][2] = 2; B.a[2][0] = 1; B.a[2][1] = 0; B.a[2][2] = 1; C.a[0][0] = 1; C.a[0][1] = 1; C.a[0][2] = 1; C.a[1][0] = 0; C.a[1][1] = 1; C.a[1][2] = 2; C.a[2][0] = 0; C.a[2][1] = 0; C.a[2][2] = 1; D.a[0][0] = 1; D.a[0][1] = 0; D.a[0][2] = 0; D.a[1][0] = 2; D.a[1][1] = 0; D.a[1][2] = 0; D.a[2][0] = 1; D.a[2][1] = 0; D.a[2][2] = 0;}inline data ksm(int y){ data ret,x = B; for (int i = 0; i < N; i++) ret.a[i][i] = 1; for (; y; y >>= 1) { if (y & 1) ret = ret * x; x = x * x; } return ret;}int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> n >> m; Pre_Work(); for (int i = 1; i <= m; i++) scanf("%d",&X[i]); for (int i = 1; i <= m; i++) A = A * ksm(X[i] - X[i - 1] - 1),A = A * C; A = A * ksm(n - X[m] - 1); A = A * D; cout << A.a[0][0] << endl; return 0;}
F:
有
n 张卡片,第i 张卡片正面写着数字Ai 背面写着数字Bi
另有n+1 张卡片,第i 张卡片正面写着数字Ci ,背面没有数字
有Q 次询问,第i 次给出Di,Ei ,将卡片(Di,Ei) 加入第一类卡片
将第一类卡片和第二类卡片逐一配对
每张第一类卡片选择正面或反面
一个配对合法,当且仅当第一类卡片选择的那一面的数字不超过和它配对的第二类卡片的数字
称一组配对的价值,为配对中第一类卡片选择正面的数量
每次询问,给出最大的价值,若无解,输出−1
1≤N,Q≤105,1≤Ai,Bi,Ci,Di,Ei≤109
solution F:
记第
i 张一类卡片选择的数字为xi ,如何判断一组决策是否合法?
一个简单的办法,将xi,ci 升序排好,若∀i,xi≤ci ,则肯定能成功配对
这里不采用这种办法,而使用另一种形式
将所有的ci 离散化,对应到[1,N]
把每个ai,bi,di,ei 对应到第一个大于等于它的ci
假设现在有一个初始值全为0的数组f
那么对于每个xi ,将[xi,N] 加上1
对于每个ci ,将[ci,N] 减去1
若配对合法,必有∀i∈[1,N],f[i]≥0
对于每个xi ,只有大于等于它的ci 才能配对,
否则前面的部分就变成负数了
先不考虑询问,令所有xi=ai ,然后处理出f 数组
考虑如果改变一张卡片的状态,则等价于[bi,ai) 加上1
那么对于一个询问,可以枚举它的状态,假设为yi
只需满足∀j<yi,fj≥0,∀j≥yi,fj≥−1
如果能预处理达到上述状态的最优决策,就能快速回答询问了
记上面这样的状态为Ansyi
初始的f 数组肯定有很多位置为负数
需要满足任意一个位置的前置条件是f 数组的每一位不小于−1
从右到做枚举每个位置,若发现fi=k<−1
则我们需要选择−k−1 张包含位置i 的卡片改变状态
贪心地想,每次取左端点尽量靠左的区间就好了
然后为了处理Ans 数组,还需从左往右扫一遍
每次贪心取右端点尽量右的区间就行了
用个堆维护,复杂度O(NlogN)
#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 INF = ~0U>>1;const int maxn = 1E5 + 10;struct d1{ int l,r,Num; d1(){} d1(int l,int r,int Num): l(l),r(r),Num(Num){} bool operator < (const d1 &B) const {return l > B.l;}};struct d2{ int l,r,Num; d2(){} d2(int l,int r,int Num): l(l),r(r),Num(Num){} bool operator < (const d2 &B) const {return r < B.r;}};int n,q,N,A[maxn],B[maxn],C[maxn],D[maxn],E[maxn],Ans[maxn],s[maxn];bool vis[maxn];vector <pair<int,int> > L[maxn],R[maxn];priority_queue <d1> Q1;priority_queue <d2> Q2;void Calc(){ for (int i = 1; i <= n; i++) for (int j = A[i]; j <= N; j += j&-j) ++s[j]; for (int i = 1; i <= n + 1; i++) for (int j = C[i]; j <= N; j += j&-j) --s[j]; int Cnt = 0; for (int i = N; i > 0; i--) { int sum = 0; for (int j = 0; j < R[i].size(); j++) Q1.push(d1(R[i][j].first,i,R[i][j].second)); for (int j = i; j > 0; j -= j&-j) sum += s[j]; if (sum >= -1) continue; sum = -sum - 1; for (int l = 0; l < sum; l++) { d1 k = Q1.top(); Q1.pop(); ++Cnt; vis[k.Num] = 1; for (int j = k.l; j <= N; j += j&-j) ++s[j]; for (int j = k.r + 1; j <= N; j += j&-j) --s[j]; } } Ans[0] = Cnt; for (int i = 1; i <= N; i++) { int sum = 0; for (int j = 0; j < L[i].size(); j++) if (!vis[L[i][j].second]) Q2.push(d2(i,L[i][j].first,L[i][j].second)); for (int j = i; j > 0; j -= j&-j) sum += s[j]; if (sum >= 0) {Ans[i] = Cnt; continue;} sum = -sum; bool pass = 1; for (int l = 0; l < sum; l++) { if (Q2.empty()) {pass = 0; break;} d2 k = Q2.top(); Q2.pop(); ++Cnt; if (k.r < i) {pass = 0; break;} for (int j = k.l; j <= N; j += j&-j) ++s[j]; for (int j = k.r + 1; j <= N; j += j&-j) --s[j]; } if (!pass) { for (int j = i; j <= N; j++) Ans[j] = INF; break; } Ans[i] = Cnt; }}void Pre_Work(){ for (int i = 1; i <= n; i++) { A[i] = lower_bound(D + 1,D + N + 1,A[i]) - D; B[i] = lower_bound(D + 1,D + N + 1,B[i]) - D; E[i] = min(A[i],B[i]); if (B[i] < A[i]) { L[B[i]].push_back(make_pair(A[i] - 1,i)); R[A[i] - 1].push_back(make_pair(B[i],i)); } } for (int i = 1; i <= n + 1; i++) C[i] = lower_bound(D + 1,D + N + 1,C[i]) - D; sort(C + 1,C + n + 2); sort(E + 1,E + n + 1); int now = 1; for (int i = 1; i <= n; i++) { while (now <= n + 1 && C[now] < E[i]) ++now; if (now == n + 2) { while (q--) puts("-1"); exit(0); } ++now; }}int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif cin >> n; for (int i = 1; i <= n; i++) scanf("%d%d",&A[i],&B[i]); for (int i = 1; i <= n + 1; i++) scanf("%d",&C[i]),D[i] = C[i]; cin >> q; sort(D + 1,D + n + 2); N = 1; for (int i = 2; i <= n + 1; i++) if (D[i] != D[i - 1]) D[++N] = D[i]; D[++N] = INF; Pre_Work(); Calc(); while (q--) { int X,Y; scanf("%d%d",&X,&Y); X = lower_bound(D + 1,D + N + 1,X) - D; Y = lower_bound(D + 1,D + N + 1,Y) - D; int now = max(n + 1 - Ans[X - 1],n - Ans[Y - 1]); printf("%d\n",now < 0 ? -1 : now); } return 0;}
- AtCoder Grand Contest 013 题解
- AtCoder Grand Contest 012 题解
- 【题解】AtCoder Grand Contest 016
- Atcoder Grand Contest 19 题解
- 题解Atcoder Grand Contest C
- AtCoder Grand Contest 013D: Piling Up 题解
- Atcoder Grand Contest 013C
- Atcoder Grand Contest 013D
- Atcoder Grand Contest 013E
- Atcoder Grand Contest 013D piling Up
- AtCoder Grand Contest 010
- AtCoder Grand Contest 011
- AtCoder Grand Contest 018
- AtCoder Grand Contest 018
- Atcoder Grand Contest 019
- AtCoder Grand Contest 010
- AtCoder Grand Contest 008
- 【思维】AtCoder Grand Contest(013)C[Ants on a Circle]题解
- 如何撰写发明专利申请文件
- 大话设计模式读书笔记(十二) 抽象工厂模式
- activemq消费者过滤器
- C语言:位异或运算符^
- 求模和取余
- AtCoder Grand Contest 013 题解
- 一个java高级工程师的进阶之路
- Windows下Sublime Text 3 + TexLive + Pandoc Markdown配置
- c语言习题
- LeetCode练习<三> 找出字符串中连续的字母
- POJ-2155-二维树状数组
- 工作中常用的正则表达式总结
- 【神经网络与深度学习】【计算机视觉】Faster R-CNN
- 逻辑判断