• 2021ICPC昆明站总结及补题


    比赛总结

    这场打的太难顶了,通过这场可以很明显看出我队综合水平不行,和其他队伍的差距太大了,前55名过题数都是在我们的3倍及以上

    由于我们综合水平低,且比赛时状态也不好,写题的策略也错了,导致这场2题打铁

    从L题讲起

    L

    看到榜上这么多人过L后,我们也就跟榜开这题了,当时我们是3个人一起想这题,还是在不算比较快,也不算比较慢的速度想到了LDS,但是代码实现却花了挺长时间,基础的LIS和LDS很久没写了,非常生疏了,A这道题时已经在64min,也算

    是我板子准备的不够充分,题练的太少了,LIS我大概是一年没见过了,而且我到现在也没写过用树状数组求LIS,比赛时只在那想开一个栈然后怎么样怎么样

    L题之后就是我写的 I 题了

    I

    计算几何是我们队的短板,当时也是跟榜写的这题,听队友讲了题意后感觉做法挺简单的,虽然都不会计算几何,但是看这做法这么简单,我就上了,写的也挺折磨的,我用的是斜截式,写完后wa了几发,就去看其它题了,这时候已经130min了,落后了很多,不应该写这题的,计算几何是我们队的不适题,而且比赛才过了第1个小时,应该先去开其他题的,写这题不仅耗时,还要看运气,应该放到后面去写

    我放弃 I 题一段时间后,队友很勇敢的上去写麻将题

    K

    麻将大模拟题,队友写了很久,都快300行了,非常辛苦,但是一直wa调不出来,赛后才发现题都读错了。。我不会打麻将,也没写过麻将题,这题就让队友承包了。我们几乎没写过大模拟,我如果写麻将这种题肯定是感觉很可怕就直接跳过,比赛时也不应该那时去写这题,因为这种题也是我们队的不适题,所耗时间以及风险都很大,而且还有好多题没有去思考,这题真的应该放到最后去写

    I题和K题是我们这次失败的主要原因,队友写K题的时候,我才开始思考其他题目,因为我之前是在写L题和 I 题,而 I 题让我思考的时间少了一个小时,K题使队友的思维时间少了两个小时左右,而我和他是队里的主要思维输出,跟榜去写这两

    题损失就很大了

    J

    队友写麻将前和我讲了这题,可惜我没有领会队友的意思,到最后还不知道pairwise distinct的意思,我到赛后才知道是原来是求最少要多少轮把它排好序,比赛时应该好好再去看题目意思的,以后要养成一个习惯,就是队友转述题目意思后,自

    己还得仔细读一下题面,由于我没有读懂这道题的题意,就放掉这题了,否则队友写麻将的时间内我绝对可以想出这题怎么写

    没有去开J题的我,向另一队友要了M题

    M

    中规中矩的数据结构题,观察出mex的性质后,要研究的问题就是怎么求一段区间小于等于mex的数之和,我在队友写麻将的时间里花了很长时间想出来怎么求,想出来了一个线段树做法,我自己也擅长写线段树板子的(指计时速写过几次线段

    树模板二),我自己的板子是用动态开点的指针写法的,当时看到1个G内存真没想到会MLE,在这之后也没想到把它改写成非指针的写法,因为当时感觉NODE里开了这么多long long数组,以为已近没戏了

    想出来M题后,我开始想C题

    C

    中规中矩的区间dp题,队友转述给我后,我并没有再去把题目条件仔细看一遍,只看了下N的范围,构造了一些情况后觉的是个N^2的dp,然后自己想了下转移方程,实际上我比赛时想的转移方程想错了,而且我比赛时并没有看到相同城市数<=15这个条件。。因为写M题时卡住了,没时间写这题了 

    下面的是比赛时没有去看的两道题

    G

    背包加一些其他东西的题,码量比较大的背包。

    D

    有意思的数学题,可以想出一个必要条件。

    综合水平低,体现在L题对LDS的不熟练,I题计算几何的短板,M题不知道是主席树,C题对区间dp的练习少。赛后补题也发现G题这个背包我也是不会这个带有时间限制的状态转移。

    如果比赛时不去开 I 题和K题,那么J题是很可能被做出来的,M题和C题不一定能做出来,M题需要我想到把线段树改写成不用指针的形式,C题则需要一起合作造样例查错才能做出来,赛后补题的时候我一直没注意C题要清空vector。

    除了H题,我们其他所有题都是有水平上的不足,所以接下来得努力练习,努力学习


    补题

    M题

    先讲一下我比赛时想出来的做法,首先观察出mex的性质,就是mex可以被 区间里小于等于mex的数之和+1 更新,反复更新mex,直到mex不能被更新为止

    所以要想办法快速求出一段区间里所有小于等于x的数之和,我开了一个线段树,用来计算一段区间内小于等于(1<<i)的数之和(记为su[i]),和一段区间内大于(1<<i)的最小的数(记为mi[i])

    记区间里小于等于x的数之和为low(x),区间里大于x的最小的数为min(x),比如初始时mex = 1,假设low(1) + 1 == 3,则更新mex = 3,假设low(2) + 1 == 6,更新mex = 6,假设low(4) + 1 == 6,此时low(4) + 1不能更新mex,那怎么判断low(6) + 1能否更新mex? 如果min(4) > 6,那么low(6) = low(4),则不能更新,如果min(4) <= 6,则low(6) >= low(4) + min(4) > low(4) + 4 > 8,那么就能更新mex,且mex是>8的,所以就能用low(8) + 1 来更新mex了

    M(非主席树)

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    using namespace std;
    const int MAXN = 1e6 + 7;
    const long long INF = 1e9 + 7;
    struct NODE {
    	int l, r;
    	long long su[31], mi[31];
    }tree[MAXN*4];
    void upd(int pos) {
    	for (int i = 0; i < 31; i++) {
    		tree[pos].su[i] = tree[pos << 1].su[i] + tree[pos << 1 | 1].su[i];
    		tree[pos].mi[i] = min(tree[pos << 1].mi[i], tree[pos << 1 | 1].mi[i]);
    	}
    }
    void build(int pos, int l, int r) {
    	tree[pos].l = l; tree[pos].r = r;
    	if (l == r) {
    		long long v;
    		scanf("%lld",&v);
    		for (int i = 0; i < 31; i++) {
    			if (v > ((long long)1 << i)) {
    				tree[pos].su[i] = 0;
    			}
    			else tree[pos].su[i] = v;
    		}
    		for (int i = 0; i < 31; i++) {
    			if (v <= ((long long)1 << i)) tree[pos].mi[i] = INF;
    			else tree[pos].mi[i] = v;
    		}
    		return;
    	}
    	int mid = l + r >> 1;
    	build(pos<<1, l, mid);
    	build(pos<<1|1, mid + 1, r);
    	upd(pos);
    }
    long long Q(int pos, int l, int r, int i) {
    	if (tree[pos].l == l && tree[pos].r == r) return tree[pos].su[i];
    	int mid = tree[pos].l + tree[pos].r >> 1;
    	if (r <= mid) return Q(pos<<1, l, r, i);
    	else if (l > mid) return Q(pos<<1|1, l, r, i);
    	else return Q(pos<<1, l, mid, i) + Q(pos<<1|1, mid + 1, r, i);
    }
    long long Q_mi(int pos, int l, int r, int i) {
    	if (tree[pos].l == l && tree[pos].r == r) return tree[pos].mi[i];
    	int mid = tree[pos].l + tree[pos].r >> 1;
    	if (r <= mid) return Q_mi(pos << 1, l, r, i);
    	else if (l > mid) return Q_mi(pos << 1 | 1, l, r, i);
    	else return min(Q_mi(pos << 1, l, mid, i), Q_mi(pos << 1|1, mid + 1, r, i));
    }
    int main()
    {
    	int n, m;
    	cin >> n >> m;
    	build(1, 1, n);
    	int l, r;
    	long long ans, mex;
    	for (int i = 1; i <= m; i++) {
    		scanf("%d%d", &l, &r);
    		l = ((long long)l + ans) % n + 1;
    		r = ((long long)r + ans) % n + 1;
    		if (l > r) swap(l, r);
    		mex = 1;
    		for (int i = 0; i < 31; i++) {
    			if (mex >= ((long long)1 << i)) mex = max(mex, Q(1, l, r, i) + 1);
    			else if(i){
    				long long lim = Q_mi(1, l, r, i - 1);
    				if (lim != INF && lim > mex) break;
    				else {
    					mex = max(mex, Q(1, l, r, i) + 1);
    				}
    			}
    		}
    		ans = mex;
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

      

     用主席树可以求出一段区间内值域在[l,r]内的数之和

    M(主席树)

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<vector>
    using namespace std;
    const int MAXN = 1e6 + 7;
    const int MAX = 1e9;
    int n, m, cnt = 0, root[MAXN], x, y;
    long long a[MAXN];
    struct NODE {
    	int l, r, siz;
    	long long su;
    }T[MAXN * 43];
    void update(int l, int r, int& x, int y, long long pos) {
    	T[x = ++cnt] = T[y], T[x].siz++, T[x].su += pos;
    	if (l == r) return;
    	int mid = l + r >> 1;
    	if (pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
    	else update(mid + 1, r, T[x].r, T[y].r, pos);
    }
    long long Q(int l, int r, int x, int y, int pos) {
    	if (pos >= r) return T[y].su - T[x].su;
    	if (pos < l) return 0;
    	if (!x && !y) return 0;
    	int mid = l + r >> 1;
    	return Q(l, mid, T[x].l, T[y].l, pos) + Q(mid + 1, r, T[x].r, T[y].r, pos);
    }
    int main()
    {
    	cin >> n >> m;
    	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    	for (int i = 1; i <= n; i++) update(1, MAX, root[i], root[i - 1], (long long)a[i]);
    	long long mex = 0, res = 0;
    	for (int i = 1; i <= m; i++) {
    		scanf("%d%d", &x, &y);
    		x = ((long long)x + mex) % n + 1;
    		y = ((long long)y + mex) % n + 1;
    		if (x > y) swap(x, y);
    		mex = 1;
    		while (1) {
    			res = Q(1, MAX, root[x - 1], root[y], min((long long )MAX,mex)) + (long long)1;
    			if (res <= mex) break;
    			mex = res;
    		}
    		printf("%lld
    ", mex);
    	}
    	return 0;
    }
    

      

    主席树可以不离散化

    C题

    很容易想到缩点,且想到ans<= 缩点后的点的个数-1,但是这之后就难了,发现可以利用相同的颜色使答案减少1

    比如1xxxxx1xxx,可以使ans - 1,1xxxx1xxx2xxxx2可以使ans - 2,也容易发现 1xx2xx1xx2这种只能使ans - 1,因为两个相同的1和两个相同的2只能选其中一个利用

    那么我们要求的是 能够同时利用来减少答案的两个同色点对的对数最多是多少对

    而且通过构造样例发现,对于1xxx1xxx1,能利用的对数是2对!!1xx1xx1xx1是3对!!

    用dp[L][R]表示L,R区间里最多能选多少对,可以利用题目给的每一个颜色最多出现15次的条件,把O(n^3)优化成O(15 n^2)

    缩点及存位置: 

    		for (int i = 1; i <= n; i++) {
    			cin >> a[i];
    			pos[i].clear();
    		}
    		int tot = 0;
    		a[++tot] = a[1];
    		for (int i = 2; i <= n; i++) {
    			if (a[i] != a[tot]) a[++tot] = a[i];
    		}
    		for (int i = 1; i <= tot; i++) {
    			pos[a[i]].push_back(i);
    		}
    

    dp[][]的转移:  

    		for (int i = 1; i <= tot; i++) {
    			for (int j = 1; j <= tot; j++) {
    				dp[i][j] = 0;
    			}
    		}
    		for (int len = 2; len <= tot; len++) {
    			for (int l = 1; l <= tot; l++) {
    				int r = l + len - 1;
    				if (r > tot)break;
    				if (a[l] == a[r]) dp[l][r] = dp[l + 1][r - 1] + 1;//比[l+1][r-1]多利用了a[l]和a[r]这一对
    				else dp[l][r] = max(dp[l][r - 1], dp[l + 1][r]);
    				for (int i = 0; i < pos[a[l]].size(); i++) {
    					int pp = pos[a[l]][i];
    					if (pp <= l) continue;
    					if (pp >= r) break;
    					dp[l][r] = max(dp[l][r], dp[l][pp] + dp[pp][r]);
    				}
    			}
    		}
    		cout << tot - 1 - dp[1][tot] << endl;
    

      

    同时也可以用正着算的方法,用f[L][R]表示[L][R]区间的最少操作数是多少

    那么枚举中间点的时候怎么转移呢

    f[L][R] = min(f[L][R],   f[L][mid] + f[mid+1][R]  )?

    还是f[L][R] = min(f[L][R],   f[L][mid] + f[mid+1][R] + 1)?

    这个地方 +1还是 不+1 ,怎么处理?

    可以发现,f[L][R]同时也可以表示:把[L][R]全部染成a[L]的颜色的最少操作数

    所以,当mid+1的颜色和L相同时,就可以不用+1,而我们取mid时,只取mid+1和L同颜色的情况,这样就ok了!

    	for (int i = 1; i <= tot; i++) {
    			for (int j = 1; j <= tot; j++) {
    				f[i][j] = INF;
    			}
    			f[i][i] = 0;
    		}
    		for (int len = 2; len <= tot; len++) {
    			for (int l = 1; l <= tot; l++) {
    				int r = l + len - 1;
    				if (a[r] == a[l]) f[l][r] = f[l][r - 1];//f[l][r-1]表示把[L][R-1]都染成L的颜色
    				else f[l][r] = min(f[l][r - 1], f[l + 1][r]) + 1;//如果a[r] != a[l],那就是把[L][R-1]染好再+1或把[L+1][R]染好再+1
    				for (int i = 0; i < pos[a[l]].size(); i++) {//初始赋值后进入这个循环
    					int pp = pos[a[l]][i];
    					if (pp <= l) continue;
    					if (pp >= r) break;
    					f[l][r] = min(f[l][r], f[l][pp - 1] + f[pp][r]);//把[L][pp-1]染成L的颜色,把[pp][R]染成pp的颜色,而pp和L同色
    				}
    			}
    		}
    		cout << f[1][tot] << endl;
    

      

    J题

    如果原排列有序,则round为0

    对于一个排列,若只有两个数位置不对,则把这两个数交换即可

    对于一个排列,若只有三个数位置不对,如2 3 1, 2想要到3的位置去,3想要到1的位置去,1想要到2的位置去,于是这便构成了一个环

    于是扫一遍原排列,则可构成一些互相独立的环,这个环可以用双向链表表示出来

    现在考虑把一个环内两个数交换一次会发生什么,可以发现会把这个环拆成两个环,两个环的size之和等于原环的size,且这两个环中各有一个数不能在这一个round里交换位置了

    于是我们可以把一个大环在一个round里把它拆成若干个2元环和一个一元环,然后在下一个round里把所有二元环解决掉

    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int MAXN = 1e5 + 7;
    int a[MAXN],pre[MAXN];
    bool vis[MAXN];
    int cnt[103];
    int ans[103][MAXN];
    int rd = 0;
    int n;
    void add(int x,int y) {
    	vis[x] = vis[y] = true;
    	ans[rd][++cnt[rd]] = x;
    	ans[rd][++cnt[rd]] = y;
    	swap(a[x], a[y]);
    	swap(pre[x],pre[y]);
    }
    void solve(int x) {
    	int len = 0;
    	while (!vis[x]) {
    		vis[x] = true;
    		len++;
    		x = a[x];
    	}
    	int t = x;
    	for (int i = 1; i <= len; i++) {
    		vis[t] = false;
    		t = a[t];
    	}
    	int y = a[x];
    	while (1) {
    		if (vis[x] || vis[y]) break;
    		if (x == y) break;
    		int xx = pre[x], yy = a[y];
    		add(x, y);
    		x = xx; y = yy;
    	}
    }
    bool ok() {
    	for (int i = 1; i <= n; i++) if (a[i] != i) return false;
    	return true;
    }
    int main()
    {
    	cin >> n;
    	for (int i = 1; i <= n; i++) {
    		cin >> a[i];
    		pre[a[i]] = i;
    	}
    	rd = 0;
    	while (!ok()) {
    		rd++;
    		cnt[rd] = 0;
    		for (int i = 1; i <= n; i++) {
    			vis[i] = false;
    		}
    		for (int i = 1; i <= n; i++) vis[i] = false;
    		for (int i = 1; i <= n; i++) {
    			if (!vis[i]) {
    				solve(i);
    			}
    		}
    	}
    	cout << rd << "
    ";
    	for (int i = 1; i <= rd; i++) {
    		cout << cnt[i] / 2;
    		for (int j = 1; j <= cnt[i]; j++) {
    			cout << " " << ans[i][j];
    		}
    		cout << "
    ";
    	}
    	return 0;
    }
    

      

    I题

    求交点然后sort就行,注意判段交点在直线上的eps的问题,缺少计算几何的板子就很难通过这题了

    队友的代码:(我们终于也会用计算几何的板子啦)

    #include <bits/stdc++.h>
    using namespace std;
    // #define int long long
    // #define double long double
    // #define endl "
    "
    const int MAXN = 1E3 + 7;
    // const int MAXE = ;
    // const int MOD = ;
    // const int INF = ;
    const double eps = 1e-8;
    // const double PI = acos(-1);
    // const int DIRX[] = {};
    // const int DIRY[] = {};
    
    int sgn(double x)
    {
    	return fabs(x) < eps ? 0 : (x < 0 ? -1 : 1);
    }
    
    struct Point
    {
    	double x, y;
    
    	Point() {};
    	Point(double x, double y) : x(x), y(y) {}
    
    	Point operator + (const Point& b) { return Point(x + b.x, y + b.y); }
    	Point operator - (const Point& b) { return Point(x - b.x, y - b.y); }
    	Point operator * (double d) { return Point(x * d, y * d); }
    
    	double dot(const Point& b) { return x * b.x + y * b.y; }
    	double det(const Point& b) { return x * b.y - y * b.x; }
    	double dis(const Point& b) { return (*this - b).dot(*this - b); }
    
    	bool operator < (const Point& b) const
    	{
    		if (x == b.x)
    			return y < b.y;
    		return x < b.x;
    	}
    } s, t, point[MAXN];
    
    typedef Point Vector;
    
    double crossProduct(Vector a, Vector b)
    {
    	return a.x * b.y - a.y * b.x;
    }
    
    double dotProduct(Vector a, Vector b)
    {
    	return a.x * b.x + a.y * b.y;
    }
    
    bool pointOnSegment(Point p, Point a, Point b)
    {
    	// sgn(crossProduct(a - p, b - p)) == 0 may cause eps problem.
    	// If p is on the line, for check on the segment, ignore this condition.
    	// return sgn(crossProduct(a - p, b - p)) == 0 && sgn(dotProduct(a - p, b - p)) < 0;
    	return sgn(dotProduct(a - p, b - p)) < 0;
    }
    
    Point lineIntersection(Point p, Vector u, Point q, Vector v)
    {
    	// Line 1: I = p + t * u
    	// Line 2: I = q + s * v
    	Vector w = p - q;
    	double t = crossProduct(v, w) / crossProduct(u, v);
    	return p + u * t;
    }
    
    int n, m;
    int cnt[MAXN];
    vector<Point> ans[MAXN];
    
    signed main(void)
    {
    	cin >> n >> m;
    	cin >> s.x >> s.y >> t.x >> t.y;
    	for (int i = 1; i <= n; ++i)
    		cin >> point[i].x >> point[i].y;
    	for (int i = 1; i <= n; ++i)
    	{
    		for (int j = i + 1; j <= n; ++j)
    		{
    			if (sgn((point[i] - point[j]).det(s - t)) == 0) // check Parallel
    				continue;
    			Point p = lineIntersection(point[i], point[j] - point[i], s, t - s);
    			if (pointOnSegment(p, s, t) == true)
    			{
    				ans[i].push_back(p);
    				ans[j].push_back(p);
    				++cnt[i]; ++cnt[j];
    			}
    		}
    	}
    	for (int i = 1; i <= n; ++i)
    	{
    		sort(ans[i].begin(), ans[i].end());
    		if (sgn(t.x - s.x) < 0 || (sgn(t.x - s.x) == 0 && sgn(t.y - s.y) < 0)) // sort condition: nearest point T
    			reverse(ans[i].begin(), ans[i].end());
    	}
    	int x, y;
    	for (int i = 1; i <= m; ++i)
    	{
    		cin >> x >> y;
    		if (cnt[x] >= y)
    			printf("%.10f %.10f
    ", ans[x][y - 1].x, ans[x][y - 1].y);
    		else
    			cout << -1 << endl;
    	}
    	return 0;
    }
    

      

    K题

    队友比赛时读错题了,把题读难了QAQ

    我还不懂麻将,什么时候去入坑雀魂qwq

    队友的代码:

    #include <bits/stdc++.h>
    using namespace std;
    // #define int long long
    // #define double long double
    // #define endl "
    "
    // const int MAXN = ;
    // const int MAXE = ;
    // const int MOD = ;
    // const int INF = ;
    // const double eps = ;
    // const double PI = acos(-1);
    // const int DIRX[] = {};
    // const int DIRY[] = {};
    
    string s;
    
    int cnt[5][13];
    
    vector<string> ans;
    
    void pre(string& s)
    {
    	memset(cnt, 0, sizeof(cnt));
    	int x, y;
    	for (int i = 0; i < 14; ++i)
    	{
    		y = s[i * 2] - '0';
    		char ch = s[i * 2 + 1];
    		if (ch == 'w')
    			x = 1;
    		if (ch == 'b')
    			x = 2;
    		if (ch == 's')
    			x = 3;
    		if (ch == 'z')
    			x = 4;
    		++cnt[x][y];
    	}
    }
    
    string mahjong(int x, int y)
    {
    	string res = "";
    	res += (char)(y + '0');
    	if (x == 1)
    		res += 'w';
    	if (x == 2)
    		res += 'b';
    	if (x == 3)
    		res += 's';
    	if (x == 4)
    		res += 'z';
    	return res;
    }
    
    bool ron(int x, int y, bool flg, int sum)
    {
    	if (flg == true && sum == 4)
    		return true;
    	if (x == 4 && y == 8)
    		return false;
    	if (x != 4 && y == 10)
    		return ron(x + 1, 1, flg, sum);
    	if (cnt[x][y] == 0)
    		return ron(x, y + 1, flg, sum);
    	if (x != 4 && cnt[x][y] > 0 && cnt[x][y + 1] > 0 && cnt[x][y + 2] > 0)
    	{
    		cnt[x][y]--;
    		cnt[x][y + 1]--;
    		cnt[x][y + 2]--;
    		bool ok = ron(x, y, flg, sum + 1);
    		cnt[x][y]++;
    		cnt[x][y + 1]++;
    		cnt[x][y + 2]++;
    		if (ok)
    			return true;
    	}
    	if (cnt[x][y] > 1 && !flg)
    	{
    		cnt[x][y] -= 2;
    		bool ok = ron(x, y, true, sum);
    		cnt[x][y] += 2;
    		if (ok)
    			return true;
    	}
    	if (cnt[x][y] > 2)
    	{
    		cnt[x][y] -= 3;
    		bool ok = ron(x, y, flg, sum + 1);
    		cnt[x][y] += 3;
    		if (ok)
    			return true;
    	}
    	return false;
    }
    
    void check(string& s)
    {
    	bool flg = false;
    	for (int i = 1; i <= 4; ++i)
    	{
    		for (int j = 1; j <= (i == 4 ? 7 : 9); ++j)
    		{
    			if (i == 4 && cnt[i][j] == 0)
    				continue;
    			if (i != 4 && cnt[i][j] == 0 && cnt[i][j - 1] == 0 && cnt[i][j + 1] == 0)
    				continue;
    			cnt[i][j]++;
    			if (ron(1, 1, false, 0))
    			{
    				flg = true;
    				s += mahjong(i, j);
    			}
    			cnt[i][j]--;
    		}
    	}
    	if (flg == true)
    		ans.push_back(s);
    }
    
    void solve()
    {
    	cin >> s;
    	pre(s);
    	if (ron(1, 1, false, 0))
    	{
    		cout << "Tsumo!" << endl;
    		return;
    	}
    	ans.clear();
    	for (int i = 1; i <= 4; ++i)
    	{
    		for (int j = 1; j <= (i == 4 ? 7 : 9); ++j)
    		{
    			if (cnt[i][j] == 0)
    				continue;
    			cnt[i][j]--;
    			string s = mahjong(i, j) + " ";
    			check(s);
    			cnt[i][j]++;
    		}
    	}
    	cout << ans.size() << endl;
    	for (int i = 0; i < ans.size(); ++i)
    	{
    		cout << ans[i] << endl;
    	}
    }
    
    signed main(void)
    {
    	ios::sync_with_stdio(false);
    	cin.tie(nullptr); cout.tie(nullptr);
    	int T;
    	cin >> T;
    	while (T--)
    		solve();
    	return 0;
    }
    

      

    G题

    G题还是比较难的,输入要判断2月29号的问题,而且不能提前continue否则会段错误! 同时这题还比较卡时间,看榜可以发现有个8题的队伍这题惨烈TLE很多发(小声),而且这题代码量也是比较大的

    m个礼物用二进制枚举选和不选,记录选 i 个礼物的可行最大价值,要把每个人按生日排序,dp的时候正序遍历,像背包那样转移

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    int dp[505][367][16];
    int mm[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    struct PP {
    	int c, v, lim;
    }pp[505];
    struct GT {
    	int c, v;
    }gt[16];
    int g[16];
    bool cmp(PP a, PP b) {
    	return b.lim > a.lim;
    }
    int main()
    {
    	int T, n, m, w;
    	cin >> T;
    	while (T--) {
    		cin >> n >> m >> w;
    		int tot = 0;
    		int ye, mon, da;
    		int ct = 0;
    		int c, v;
    		memset(g, 0, sizeof(g));
    		for (int i = 1; i <= n; i++) {
    			scanf("%d-%d-%d", &ye, &mon, &da);
    			cin >> c >> v;
    			if (mon == 2 && da == 29) continue;
    			ct++;
    			int d = 0;
    			for (int j = 1; j < mon; j++) d += mm[j];
    			d += da;
    			if (ye > 2021) continue;
    			pp[ct].c = c; pp[ct].v = v; pp[ct].lim = d;
    		}
    		n = ct;
    		sort(pp + 1, pp + n + 1, cmp);
    		for (int i = 1; i <= m; i++) g[i] = 0;
    		for (int i = 1; i <= m; i++) cin >> gt[i].c >> gt[i].v;
    		for (int i = 1; i < (1 << m); i++) {
    			int cnt = 0, co = 0, val = 0;
    			for (int j = 0; j < m; j++) {
    				if ((i >> j) & 1) {
    					cnt++;
    					co += gt[j + 1].c;
    					val += gt[j + 1].v;
    				}
    			}
    			if (co <= w)g[cnt] = max(g[cnt], val);
    		}
    		for (int i = 0; i <= n; i++) {
    			for (int day = 0; day <= 365; day++) {
    				for (int j = 0; j <= m; j++) {
    					dp[i][day][j] = 0;
    				}
    			}
    		}
    		int ans = 0;
    		for (int i = 1; i <= n; i++) {
    			for (int day = 1; day <= 365; day++) {
    				for (int j = 0; j <= min(i,m); j++) {
    					dp[i][day][j] = dp[i - 1][day][j];
    					if (day >= pp[i].c && day <= pp[i].lim) dp[i][day][j] = max(dp[i][day][j], dp[i - 1][day - pp[i].c][j] + pp[i].v);
    					if (j) dp[i][day][j] = max(dp[i][day][j], dp[i - 1][day][j - 1] + (g[j] - g[j - 1]));
    				}
    			}
    		}
    		for (int i = 0; i <= m; i++) {
    			for (int day = 1; day <= 365; day++) {
    				ans = max(ans, dp[n][day][i]);
    			}
    		}
    		cout << ans << endl;
    	}
    	return 0;
    }
    

      

    D题

    讲一下题意,就是两个人组成一对和机器人玩游戏,一局游戏里,给定n,k, 机器人会造一个长度为n的数列,数列中的每个数在[0,k)范围里,然后把数列和一个数字p(0<=p<n) 交给队员1,他必须要在数列里选一个数,把这个数+1,

    再%k,然后把这个经过修改后的数列交给队友,但是他不能把数字p告诉队友,然后队友会猜数字p是什么,且队友只能提交一次答案,问是否有必胜策略

    如果n为1,那么p只能为0,人类必赢

    如果n为2,k为2,那么机器人构造的数列可以是{0,0}或{1,0}或{0,1}或{1,1},机器人给的p可以是0或1

    可以构造出一个必胜策略:

    如果p是0,机器人构造{0,0}或{1,1},队员1就把它改成{0,1},机器人构造{0,1}或{1,0},队员1就把它改成{1,1}

    如果p是1,机器人构造{0,0}或{1,1},队员1就把它改成{1,0},机器人构造{0,1}或{1,0},队员1就把它改成{0,0}

    这样,队员2收到{0,1}或{1,1},就输出0,收到{1,0}或{0,0},就输出1

    机器人构造的数列一共有k^n种情况,把这些数列想象成k^n个点,每个点连接n个点,表示这个点可以通过一次修改可以变成的点,每个点还有一个颜色,一共有n种颜色,分别代表p的位置

    于是每个点连接的n个点的颜色一定要是不同的,这样人类就有必胜策略,而要满足这个条件,每个点不能连接两个同样颜色的点,每个绿色点可以给n个点贡献绿色,那么绿色点的个数为k^n / n,其他颜色点的个数与其一样

    于是得到一个必要条件,就是(k^n) % n == 0

    至于这个条件是否为充要条件,就要去看出题人发的那个很长的证明了(我不会qwq)

    #include<iostream>
    #include<algorithm>
    using namespace std;
    long long gcd(long long a, long long b) {
    	if (a < b) swap(a, b);
    	while (b) {
    		a = a % b;
    		swap(a, b);
    	}
    	return a;
    }
    int main()
    {
    	int T;
    	cin >> T;
    	long long n, k;
    	while (T--) {
    		cin >> n >> k;
    		long long gg;
    		while ((gg = gcd(n, k)) != 1) {
    			n /= gg;
    		}
    		if (n == 1) cout << "HUMAN
    ";
    		else cout << "ROBOT
    ";
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    P1113 杂务 题解
    P3916 图的遍历 题解
    P5318 【深基18.例3】查找文献 题解
    P2814 家谱 题解
    P3879 [TJOI2010]阅读理解 题解
    P4305 不重复的数字题解
    P1955 [NOI2015] 程序自动分析题解
    P1892 [BOI2003]团伙
    P1525 [NOIP2010 提高组] 关押罪犯
    【610】keras 相关问题说明
  • 原文地址:https://www.cnblogs.com/ruanbaitql/p/14623110.html
Copyright © 2020-2023  润新知