Gyk的题是真的仙
(题面太长,就不复制了)
Gyk一开始就扔一个重磅炸弹,虽然是T1, 但毕竟是NOI,让人望而生畏, 所以考试时我是最后只打了一个暴力,拿了28分
解析: 线段树 + 压位
先不考虑数据范围,先考虑如何维护小数据, 可以发现,在二进制加法中,在某一位加1,若该位为0, 则直接加;若该位为1, 则需进位, 一直进到这一位的更高位中第一个0的位置
减1同理,若该位为1, 则直接减;若该位为0, 则需退位, 一直退到这一位的更高位中第一个1的位置(数据保证了总有更高位的1)
那就是很明显的一道线段树区间覆盖、单点修改的问题了,这时线段树的底部一个点是x的二进制位上的数,是0或1
显然这样会T
观察k的范围,30n, 强有力的暗示, 我们采取压位,把进制调为230,线段树的底部一个点存了x二进制连续30位组成的数
再考虑加减法,先找到要修改的第一个区间,即线段树底部的第(y/30+1)号节点,计算出在当前节点的改变量,若要进退位,也最多进退1, 在右边的区间内查询第一个0或1的位置(线段树维护)来完成进退位, 再区间覆盖、单点修改即可
注意到a的绝对值不超过1e9,因此最多像上面那样操作两次, 就可以完成修改
代码:
#include<bits/stdc++.h> using namespace std; const int maxn = 1000010, Base = (1<<30), inf = maxn - 9; template<class T> inline void read(T &ret) { T fl = 1; char ch; while((ch=getchar()) && (ch < '0' || ch > '9')) if(ch == '-') fl = -1; ret = ch - '0'; while((ch=getchar()) && ch >= '0' && ch <= '9') ret = (ret<<3) + (ret<<1) + ch - '0'; ret *= fl; } int Q, n, tot, root; struct seg_tree{ int l, r, ls, rs, tag, fl[2]; int sum; }tr[maxn<<1]; inline void update(int x) { tr[x].fl[0] = min(tr[tr[x].ls].fl[0], tr[tr[x].rs].fl[0]); tr[x].fl[1] = min(tr[tr[x].ls].fl[1], tr[tr[x].rs].fl[1]); } inline void Newnode(int &x, int l, int r) { x = ++tot; tr[x].l = l; tr[x].r = r; tr[x].fl[0] = l; tr[x].fl[1] = inf; tr[x].tag = -1; } inline void spread(int x) { if(tr[x].tag != -1) { tr[tr[x].ls].tag = tr[tr[x].rs].tag = tr[x].tag; if(tr[x].tag == 0) { tr[tr[x].ls].fl[1] = tr[tr[x].rs].fl[1] = inf; tr[tr[x].ls].fl[0] = tr[x].l; tr[tr[x].rs].fl[0] = tr[tr[x].rs].l; } else { tr[tr[x].ls].fl[1] = tr[x].l; tr[tr[x].rs].fl[1] = tr[tr[x].rs].l; tr[tr[x].ls].fl[0] = tr[tr[x].rs].fl[0] = inf; } tr[x].tag = -1; } } inline void Update2(int x) { if(tr[x].tag == -1) return ; if(tr[x].tag == 0) tr[x].sum = 0; else if(tr[x].tag == 1) tr[x].sum = Base - 1; tr[x].tag = -1; } inline void Ad(int x, int id, int v) { if(tr[x].l == tr[x].r) { Update2(x); tr[x].sum += v; tr[x].fl[0] = (tr[x].sum != Base - 1? tr[x].l: inf); tr[x].fl[1] = (tr[x].sum != 0? tr[x].l : inf); return ; } int mid = (tr[x].l + tr[x].r)>>1; if(!tr[x].ls) Newnode(tr[x].ls, tr[x].l, mid); if(!tr[x].rs) Newnode(tr[x].rs, mid + 1, tr[x].r); spread(x); if(id <= mid) Ad(tr[x].ls, id, v); else Ad(tr[x].rs, id, v); update(x); } inline int Query(int x, int id) { if(tr[x].l == tr[x].r) { Update2(x); return tr[x].sum; } int mid = (tr[x].l + tr[x].r) >> 1; if(!tr[x].ls) Newnode(tr[x].ls, tr[x].l, mid); if(!tr[x].rs) Newnode(tr[x].rs, mid + 1, tr[x].r); spread(x); int ret; if(id <= mid) ret = Query(tr[x].ls, id); else ret = Query(tr[x].rs, id); update(x); return ret; } inline int Find(int x, int l, int r, int v) { if(l <= tr[x].l && tr[x].r <= r) { if(tr[x].l == tr[x].r) Update2(x); return tr[x].fl[v]; } int mid = (tr[x].l + tr[x].r)>>1; if(!tr[x].ls) Newnode(tr[x].ls, tr[x].l, mid); if(!tr[x].rs) Newnode(tr[x].rs, mid + 1, tr[x].r); spread(x); int ret = inf; if(l <= mid) ret = min(ret, Find(tr[x].ls, l, r, v)); if(ret == inf && mid < r) ret = min(ret, Find(tr[x].rs, l, r, v)); update(x); return ret; } inline void Change(int x, int l, int r, int v) { if(l <= tr[x].l && tr[x].r <= r) { tr[x].fl[v] = tr[x].l; tr[x].fl[v^1] = inf; tr[x].tag = v; if(tr[x].l == tr[x].r) Update2(x); return ; } int mid = (tr[x].l + tr[x].r)>>1; if(!tr[x].ls) Newnode(tr[x].ls, tr[x].l, mid); if(!tr[x].rs) Newnode(tr[x].rs, mid + 1, tr[x].r); spread(x); if(l <= mid) Change(tr[x].ls, l, r, v); if(mid < r) Change(tr[x].rs, l, r, v); update(x); } inline void Add(int x, int v) { int t = Query(root, x); if(t + v < Base) Ad(root, x, v); else { Ad(root, x, v - Base); int q = Find(root, x + 1, n, 0); if(x + 1 < q) Change(root, x+1, q-1, 0); Ad(root, q, 1); } } inline void Del(int x, int v) { int t = Query(root, x); if(t - v >= 0) Ad(root, x, -v); else { Ad(root, x, Base - v); int q = Find(root, x+1, n, 1); if(x + 1 < q) Change(root, x + 1, q - 1, 1); Ad(root, q, -1); } } int main() { //freopen("integer.in", "r", stdin); //freopen("integer.out", "w", stdout); int z; read(Q);read(z);read(z);read(z); n = maxn - 10; Newnode(root, 1, n); while(Q--) { int opt; read(opt); if(opt == 1) { int x, y; read(x);read(y); int p = y / 30 + 1; if(x > 0) { Add(p, x << (y%30) & (Base - 1)); Add(p + 1, x >> (30 - y%30)); } else if(x < 0) { x = -x; Del(p, x << (y%30) & (Base - 1)); Del(p + 1, x >> (30 - y%30)); } } else { int x; read(x); int t = Query(root, x/30 + 1); putchar(((t>>(x%30))&1)+'0'); putchar(' '); } } return 0; }
T1后记:虽然在各大OJ都可AC, 唯独hfu老爷机过不了, 也许是常数太大吧,被卡了5个点,还希望各路巨佬多多指教
T2 便(then)
【题目描述】
给出一个R*C 的棋盘.共有R 行C 列,R*C 个格子.现要在每个格子都填一个非
负整数.使得任意一个2*2 的正方形区域都满足这样的性质:左上角的数字+右下
角的数字=左下角的数字+右上角的数字.有些格子已经确定,你不能更改其中的
数字.其他格子的数字由你决定.
这是一个符合要求的3*3 的棋盘:
1 | 2 | 3 |
2 | 3 | 4 |
4 | 5 | 6 |
不难验证每个2*2 的区域都是符合要求的.
Orbitingflea 想要知道一个可行的填充棋盘的方案.但是这个方案可能很大.
所以你只需对给定的棋盘判定是否存在至少一种可行的填充棋盘的方案.
【输入格式】
第一行输入一个T,表示数据组数。接下来T 组数据。
每组数据的第1 行2 个整数R,C 表示棋盘的大小.
第2 行1 个整数n 表示已经被填好数字的格子的数目.
接下来n 行每行3 个整数ri,ci,ai,表示第ri 行ci 列的格子被填上了数字ai.
【输出格式】
T 行.第i 行是第i 组数据的答案.有合法方案时输出一行Yes,没有时输出一行No.
【样例输入】
6
2 2
3
1 1 0
1 2 10
2 1 20
2 3
5
1 1 0
1 2 10
1 3 20
2 1 30
2 3 40
2 2
3
1 1 20
1 2 10
2 1 0
3 3
4
1 1 0
1 3 10
3 1 10
3 3 20
2 2
4
1 1 0
1 2 10
2 1 30
2 2 20
1 1
1
1 1 -1
【样例输出】
Yes
No
No
Yes
No
No
【数据范围】
第1 个测试点,R=1
第2,3 个测试点,R*C<=12,如果有解,保证存在一个解使得所有数字大小不超过2
第4,5 个测试点,R=2
第6,7 个测试点,R=3
第8 个测试点,1<=R,C<=20
第9 个测试点,1<=R,C<=100
对于全部测试点,1<=T<=6,1<=R,C,n<=100000,1<=ri<=R,1<=ci<=C,同一个
格子不会多次被填上数字.ai 是整数且绝对值不超过10^9.
解析:带权并查集
其实在今年的寒假集训中,lrd出了一道一样的题(gyk也承认就是lrd的题),但我当时就爆0了,又没有挠明白,所以这次也就只有30分,好在这次挠明白了
我们设一个2*2的格子值为a, b, c, d(如图)
a | b |
c | d |
由题意得:a + d = c + b, 然后变一下型有:a - c = b - d
可以推广一下发现任意两行之间的差值是一样的, 维护一个带权并查集,用点代表行,点到父亲的权值即为两行之间的差值,出现矛盾即为两个点在同一集合内且,两行的已有差值不等于两行上另外两个在同一列上的点权的差(这里的行也可以是列,但没有必要两个一起判断,这两个是等价的,只是式子移项时不同而已,本质是相同的)
但题中还有一个蛇皮条件,格子中每个数必须为非负数,很烦
考虑到,我们已经确定了在同一集合内的两行的差,那么这个集合内的最小行(用行中最小点权代表这一行的权值)大于0就可满足题意,否则不行,我们先处理处一个集合内的父亲行的最小值,再用父亲行的最小值减去与父亲行的差的最大值,即为集合内的最小行的权值
代码:
#include<bits/stdc++.h> using namespace std; const int maxn = 100005; int T, n, f[maxn], dis[maxn], R, C; int Min1[maxn], Min2[maxn]; struct point{ int a, b, val; }p[maxn]; int Find(int x) { if(x == f[x]) return x; int t = Find(f[x]); dis[x] += dis[f[x]]; return f[x] = t; } bool cmp(point x, point y) { return x.b < y.b; } bool Link(int x, int y, int v) { int fx = Find(x), fy = Find(y); if(fx != fy) { f[fy] = fx; dis[fy] = dis[x] + v - dis[y]; return 1; } else return dis[y] - dis[x] == v? 1 : 0; } int main() { freopen("then.in", "r", stdin); freopen("then.out", "w", stdout); scanf("%d", &T); while(T--) { bool fl = 0; scanf("%d%d%d", &R, &C, &n); memset(Min1,0x3f3f,sizeof(Min1)); memset(Min2,0x3f3f,sizeof(Min2)); for(int i = 1; i <= R ; ++i) f[i] = i, dis[i] = 0; for(int i = 1; i <= n ; ++i) { scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].val); if(p[i].val < 0) fl = 1; } sort(p+1, p+n+1, cmp); for(int i = 2; i <= n ; ++i) if(p[i].b == p[i-1].b) if(!Link(p[i-1].a, p[i].a, p[i].val - p[i-1].val)) fl = 1; for(int i = 1 ; i <= n ; ++i) { int q = Find(p[i].a); Min1[q] = min(Min1[q], p[i].val - dis[p[i].a]); } for(int i = 1; i <= R; ++i) { int q = Find(i); Min2[q] = min(Min2[q], dis[i]); } for(int i = 1; i <= R; ++i) if(i == f[i] && Min1[i] + Min2[i] < 0) fl = 1; printf("%s ", fl?"No":"Yes"); } }
T3: play
题目大意
这个位面存在编号为1~2N的2N个斗士,他们正为争夺斗士大餐展开R轮PVP,每个斗士i都有一个固有APM ai,和一个初始斗士大餐储量 bi。每轮开始前以及最后一轮结束之后,2N个斗士会重新按照各自斗士大餐的储量进行排序(斗士大餐储量相同时编号小的靠前),每轮中,第1名和第2名PVP,第3名和第4名PVP,……第2k-1名和第2k名PVP,第2N-1名和第2N名PVP。而每场一对一的PVP都非常无聊,总是两个斗士中APM高的获胜,另一方失败;或者APM相同的两方取得平手。每轮赛后获胜方获得2份斗士大餐,平均双方均获得1份斗士大餐。
求输出最后排名从小到大的各斗士编号。
输入格式
第一行两个整数N、R;
第二行2N个整数,bi;
第三行2N个整数, ai;
输出格式
2N个整数,R轮比赛后排名从小到大的各斗士编号;
样例输入
10 10
0 10 49 24 7 1 64 8 52 81 4 9 40 17 52 17 40 0 97 77
0 1 0 1 1 1 0 2 1 0 0 2 1 1 2 0 1 1 1 0
样例输出
19 10 20 7 15 9 13 17 3 4 14 12 8 2 16 5 6 18 11 1
数据范围
10%的数据:N≤10,R≤10,ai≤1e8,bi≤1e8
30%的数据:N≤1e2,R≤60,ai≤1e8,bi≤1e8
70%的数据:N≤1e4,R≤60,ai≤1e8,bi≤1e8
100%的数据:N≤1e5,R≤60,ai≤1e8,bi≤1e8
解析:加速排序
先暴力,如果每一回合快排一次的话就是O(RN*logN)的,好像能过耶,于是我考试时就这样写了(主要是我想不出来O(RN)的排序了),于是rp--,只有60分,他们用相同的思想有70分,我真的无语
我们在一开始用快排预处理出第一个有序的序列,然后两两打架,发现如果把b值加0,加1, 加2的人分别提取出来, 存到三个数组里,这三个数组依然是有序的(即满足题中的排序方式),于是我们依次比较三个数组的队首元素,比较,取出,存入新的数组,这样就可以在O(n)时间内得到一个有序序列了
这个毒瘤gyk搞的输入顺序,我还真的调试了一会儿
代码:
#include<bits/stdc++.h> typedef long long ll; using namespace std; const int maxn = 200005; int n, R, qA, qB, qC, pA, pB, pC, Q; struct player{ int num; ll a, b; }p[maxn], A[maxn], B[maxn], C[maxn]; bool cmp(player x, player y) { if(x.b == y.b) return x.num < y.num ; return x.b > y.b ; } int main() { freopen("play.in", "r", stdin); freopen("play.out", "w", stdout); scanf("%d%d", &n, &R); n *= 2; for(int i = 1 ; i <= n ; ++i) scanf("%d", &p[i].b); for(int i = 1 ; i <= n ; ++i) scanf("%d", &p[i].a), p[i].num = i; sort(p + 1, p + n + 1 , cmp); for(int i = 1; i <= R; ++i) { memset(A,0,sizeof(A)); memset(B,0,sizeof(B)); memset(C,0,sizeof(C)); qA = qB = qC = 0; for(int j = 1; j <= n ; j += 2) { if(p[j].a > p[j+1].a) { p[j].b += 2; A[++qA] = p[j]; B[++qB] = p[j+1]; } else if(p[j].a < p[j+1].a) { p[j+1].b += 2; A[++qA] = p[j+1]; B[++qB] = p[j]; } else { p[j].b++; p[j+1].b++; C[++qC] = p[j]; C[++qC] = p[j+1]; } } pA = pB = pC = 1; for(int j = 1; j <= n ; ++j) { if(pA > qA && pB > qB) p[j] = C[pC++]; else if(pC > qC && pB > qB) p[j] = A[pA++]; else if(pA > qA && pC > qC) p[j] = B[pB++]; else if(A[pA].b > B[pB].b && A[pA].b > C[pC].b) p[j] = A[pA++]; else if(B[pB].b > A[pA].b && B[pB].b > C[pC].b) p[j] = B[pB++]; else if(C[pC].b > A[pA].b && C[pC].b > B[pB].b) p[j] = C[pC++]; else if(C[pC].b == A[pA].b && C[pC].b == B[pB].b) { if(A[pA].num < B[pB].num) { if(A[pA].num < C[pC].num) p[j] = A[pA++]; else p[j] = C[pC++]; } else { if(B[pB].num < C[pC].num) p[j] = B[pB++]; else p[j] = C[pC++]; } } else if(A[pA].b == B[pB].b) p[j] = A[pA].num < B[pB].num ? A[pA++] : B[pB++]; else if(A[pA].b == C[pC].b) p[j] = A[pA].num < C[pC].num ? A[pA++] : C[pC++]; else if(C[pC].b == B[pB].b) p[j] = C[pC].num < B[pB].num ? C[pC++] : B[pB++]; } } for(int i = 1 ; i <= n ; ++i) printf("%d ", p[i].num); return 0; }
其实这个题和普及组的瑞士轮有点相似,只是多了一种情况,当然这种算法还是比较经典的在NOIP2016蚯蚓一题中也有用到
总结:
这次考试都与DS有关,最终得了108分28 + 20 + 60,可见数据结构我还不能灵活运用,必须找点时间做点数据结构的题来练一练
2019-07-10