zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 041 简要题解

    从这里开始

    Problem A Table Tennis Training

      如果两个人位置奇偶性相同,那么一定是两个人同时往中间走。

      否则是两个人走到边上使得奇偶性相同,然后再像上面那样做。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    #define ll long long
    
    ll n, a, b;
    
    int main() {
    	scanf("%lld%lld%lld", &n, &a, &b);
    	ll ans = -1;
    	if (!((b - a) & 1)) {
    		ans = (b - a) >> 1;
    	} else {
    		ans = min(a, n - b + 1) + ((b - a - 1) >> 1);
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }

    Problem B Voting Judges

      不难发现,初试分数越大比越小的题目有更大的可能性可行。考虑二分答案,问题是怎么 check。

      条件相当于是要求严格比它的大的至多有 $P - 1$ 个。

      考虑先找最大的 $P - 1$ 个每个人都投。剩下的题目是让尽可能多的人投。

      每次分配投的人的时候让已经投的题数最少的人投,这样至多有 2 种投的题数的人。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    const int N = 1e5 + 5;
    
    int n, m, V, P;
    int a[N];
    
    boolean check(int mid) {
    	int hig = V - 1, r1 = m, r2 = 0;
    	int cnt = 0, r = n;
    	for (r = n; r && cnt < P - 1; r--) {
    		if (r == mid)
    			continue;
    		hig--, cnt++;
    	}
    	for (int i = 1; i <= r; i++) {
    		if (i == mid)
    			continue;
    		if (a[i] > a[mid] + m)
    			return false;
    		int need = min(m, a[mid] + m - a[i]);
    		if (r1 > need) {
    			r1 -= need;
    			r2 += need;
    		} else {
    			r1 = need - r1;
    			r2 = m - r1;
    			swap(r1, r2);
    			hig--;
    		}
    	}
    	return hig <= 0;
    }
    
    int main() {
    	scanf("%d%d%d%d", &n, &m, &V, &P);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", a + i);
    	}
    	sort(a + 1, a + n + 1);
    	int l = 1, r = n;
    	while (l <= r) {
    		int mid = (l + r) >> 1;
    		if (check(mid)) {
    			r = mid - 1;
    		} else {
    			l = mid + 1;
    		}
    	}
    	printf("%d
    ", n - r);
    	return 0;
    }

    Problem C Domino Quality

      当 $n < 3$ 的时候显然无解。

      手玩 $n = 3, 4, 5, 6, 7$,除了 $n = 3$ 的时候要求每行每列恰好有 1 个,剩下都要求有 3 个,然后剩下可以拿它们凑一下就行了。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    const int N = 1005;
    
    char ans3[12][12] = {"aa.", "..a", "..a"};
    char ans4[12][12] = {"aacd", 
    					 "bbcd",
    					 "dcaa",
    					 "dcbb"};
    
    char ans5[12][12] = {"aabc.", 
    					 "..bcd", 
    					 "iij.d", 
    					 "n.jaa", 
    					 "nkkbb"};
    
    char ans7[12][12] = {"aade...", 
    					 "bbde...", 
    					 "ccffg..", 
    					 "..i.gee", 
    					 "..ihhdd", 
    					 "....cba", 
    					 "....cba"};
    
    char ans10[12][12] = {"aacd......", 
    					  "bbcd......", 
    					  "..eeab....", 
    					  "..ffab....", 
    					  "ef..cc....", 
    					  "ef..dd....", 
    					  "......aacd", 
    					  "......bbcd", 
    					  "......dcaa", 
    					  "......dcbb"};
    
    int n;
    char ans[N][N];
    
    void put(char (*T)[12], int x, int d) {
    	for (int i = 0; i < d; i++) {
    		for (int j = 0; j < d; j++) {
    			ans[x + i][x + j] = T[i][j];
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	for (int i = 0; i < n; i++) {
    		for (int j = 0; j < n; j++) {
    			ans[i][j] = '.';
    		}
    	}
    	if (n == 1 || n == 2) {
    		puts("-1");
    		return 0;
    	} else if (!(n % 3)) {
    		for (int i = 0; i < n; i += 3)
    			put(ans3, i, 3);	
    	} else if (n == 4) {
    		put(ans4, 0, 4);
    	} else if (n == 5) {
    		put(ans5, 0, 5);
    	} else if (n == 7) {
    		put(ans7, 0, 7);
    	} else if (n == 10) {
    		put(ans10, 0, 10);
    	} else if (n == 11) {
    		put(ans4, 0, 4);
    		put(ans7, 4, 7);
    	} else {
    		int c4 = 0, t = 0;
    		while ((n - c4 * 4) % 5)
    			c4++;
    		for (int i = 0; i < c4; i++, t += 4)
    			put(ans4, t, 4);
    		while (t < n)
    			put(ans5, t, 5), t += 5;
    	}
    	for (int i = 0; i < n; i++)
    		puts(ans[i]);
    	return 0;
    }

    Problem D Problem Scores

    一. (材料分析题) 请根据下列材料回答问题

      材料1 神仙 jerome_wei 已经手持 2 枚国际金牌,而 yyf 有 0 块。

      材料2 神仙 jerome_wei 在 csp-s 2019 上取得了全国前 10,四川第一的优异成绩

      1. 神仙 jerome_wei 过了 D 后,跑来对 yyf 说:

       请简要分析,这句话体现神仙 jerome_wei 怎样的特点。

      2. 请简要分析造成这样现象的原因。


      条件可以转化成长度为 $left lceil frac{n - 1}{2} ight ceil + 1$ 的前缀的和大于长度为 $left lceil frac{n - 1}{2} ight ceil$ 的后缀的和。

      枚举中间的数的值 $x$,然后把所有数先填成这个值,然后左侧可以减少一些值,右侧可以加上一些值,它们的改变量之和小于 $x$。

      问题相当于将 $x$ 拆成不超过 某个接近 $n/2$ 的数 个不超过 $y$ 的数的方案数。 

      不考虑个数限制是个 trivial dp。因为这个数加一的和的两倍比 $n - 1$ 大,所以可以枚举有多少个数,把它们都减 1,这样就能算不合法方案数。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    #define ll long long
    
    int Mod;
    
    typedef class Zi {
    	public:
    		int v;
    
    		Zi() : v(0) {	}
    		Zi(int v) : v(v) {	}
    		Zi(ll x) : v(x % Mod) {	}
    
    		friend Zi operator + (Zi a, Zi b) {
    			return ((a.v += b.v) >= Mod) ? (a.v - Mod) : a; 
    		}
    		friend Zi operator - (Zi a, Zi b) {
    			return ((a.v -= b.v) < 0) ? (a.v + Mod) : a; 
    		}
    		friend Zi operator * (Zi a, Zi b) {
    			return 1ll * a.v * b.v;
    		}
    } Zi;
    
    const int N = 5e3 + 5;
    
    int n;
    Zi f[N][N], g[N][N];
    
    void work(Zi (*f)[N], int lim) {
    	f[0][0] = 1;
    	for (int i = 1; i <= n; i++) {
    		for (int j = 0; j < i; j++)
    			f[i][j] = f[i - 1][j];
    		for (int j = i; j <= n; j++) {
    			f[i][j] = f[i - 1][j] + f[i][j - i];
    		}
    	}
    	for (int i = n; i; i--) {
    		Zi sum = 0;
    		for (int j = lim + 1; j <= n; j++) {
    			sum = sum + f[i - 1][j - lim - 1];
    			f[i][j] = f[i][j] - sum;
    		}
    	}
    }
    
    int main() {
    	scanf("%d%d", &n, &Mod);
    	int hn = (n + 1) >> 1;
    	Zi ans = 0;
    	if (n & 1) {
    		work(f, hn - 1);
    		work(g, hn - 1);
    	} else {
    		work(f, hn);
    		work(g, hn - 1);
    	}
    	for (int i = 0; i <= n; i++) {
    		for (int j = 1; j <= n; j++) {
    			g[i][j] = g[i][j] + g[i][j - 1];
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = 0; j < i; j++) {
    			ans = ans + f[i - 1][j] * g[n - i][i - j - 1];
    		}
    	}
    	printf("%d
    ", ans.v);
    	return 0;
    }

    Problem E Balancing Network

      第一问大概就是考虑最终能到一根线的所有起始位置,如果它不是全集就无解,否则存在解。如果有一条在 $(x, y)$ 间的边,最终到了 $x$,那么可能的起始位置是两边的并,求这个可以用 bitset 简单搞一搞,求一下最后经过第 $i$ 条边所有的起始位置,然后简单构造一下。

      语文不太好,讲不清楚,sad.....

      第二问就是你发现当 $n = 3$ 的时候一定有解,只考虑前三根线。假设当前在 $a, b$ 两根线的最终会到不同线,并且下一条边不是 $(a, b)$,考虑下下一条边如果是 $(a, b)$,你就让这一根线做一点调整就行了,显然它仍然满足这个限制。

      时间复杂度 $O(m)$

      另外简单说一下神仙 jerome_wei 的做法。考虑建 $n$ 个点,表示初始在每个线,然后给每条边建一个点。对于每条线向第一次遇到的边连边,每条边向两端下一个会到的边连边。建一个源点和汇点,源点向初始的 $n$ 个点连边,如果某个点之后不会再遇到任何边,那么向汇点连边。

      问题可以转化为求两条除去起点终点外点不相交的路径。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    #define pii pair<int, int>
    
    const int N = 5e4 + 5;
    const int M = 1e5 + 5;
    
    int n, m, type;
    char ans[M];
    int us[M], vs[M];
    
    namespace subtask1 {
    
    	bitset<N> f[M];
    	boolean vis[M];
    	int h[N], A[M], B[M];
    
    	void F(int p, int x) {
    		if (!p || vis[p]) {
    			return;
    		}
    		vis[p] = true;
    		if (x == us[p]) {
    			F(A[p], x);
    			F(B[p], vs[p]);
    		} else {
    			ans[p] = 'v';
    			F(B[p], x);
    			F(A[p], us[p]);
    		}
    	}
    
    	void solve() {
    		for (int i = 1, u, v; i <= m; i++) {
    			u = us[i], v = vs[i];
    			if (!h[u]) {
    				f[i].set(u);	
    			} else {
    				f[i] = f[h[u]]; 
    			}
    			swap(u, v);
    			if (!h[u]) {
    				f[i].set(u);	
    			} else {
    				f[i] |= f[h[u]]; 
    			}
    			swap(u, v);
    			A[i] = h[u], B[i] = h[v];
    			h[u] = h[v] = i;
    		}
    		for (int i = 1; i <= m; i++)
    			ans[i] = '^';
    		for (int i = 1; i <= m; i++) {
    			if ((signed) f[i].count() == n) {
    				F(i, us[i]);
    				puts(ans + 1);
    				return;
    			}
    		}
    		puts("-1");
    	}
    
    }
    
    namespace subtask2 {
    	
    	const int C = 451;
    
    	set<pii> S;
    	int h[C];
    	int nxt[M][C];
    	bitset<C> vis[M][2];
    
    	boolean dfs(int p, int x, int y) {
    		if (x == y) {
    			return false;
    		}
    		if (p == m + 1) {
    			return true;
    		}
    		int sx = -1, sy = -1;
    		if (x == us[p] || y == us[p]) {
    			sx = 0, sy = x ^ y ^ us[p];
    		} else {
    			sx = 1, sy = x ^ y ^ vs[p];
    		}
    		if (vis[p][sx].test(sy))
    			return false;
    		vis[p][sx].set(sy);
    
    		// down
    		int nx = (x == us[p]) ? vs[p] : x;
    		int ny = (y == us[p]) ? vs[p] : y;
    		if (dfs(min(nxt[p][nx], nxt[p][ny]), nx, ny)) {
    			ans[p] = 'v';
    			return true;
    		}
    
    		nx = (x == vs[p]) ? us[p] : x;
    		ny = (y == vs[p]) ? us[p] : y;
    		if (dfs(min(nxt[p][nx], nxt[p][ny]), nx, ny)) {
    			ans[p] = '^';
    			return true;
    		}
    		return false;
    	}
    
    	void solve() {
    		for (int i = 1; i <= m; i++) {
    			S.insert(pii(us[i], vs[i]));
    		}
    		if ((signed) S.size() < 1ll * n * (n - 1) / 2) {
    			for (int i = 1; i <= n; i++) {
    				for (int j = i + 1; j <= n; j++) {
    					if (!S.count(pii(i, j))) {
    						for (int k = 1; k <= m; k++) {
    							if (us[k] == i || us[k] == j) {
    								ans[k] = '^';
    							} else {
    								ans[k] = 'v';
    							}
    						}
    						puts(ans + 1);
    						return;
    					}
    				}
    			}
    			assert(false);
    			return;
    		}
    		assert(n < C);
    		for (int i = 1; i <= m; i++)
    			ans[i] = 'v';
    		for (int i = 1; i <= n; i++)
    			h[i] = m + 1;
    		for (int i = m; i; i--) {
    			memcpy(nxt[i], h, sizeof(h));
    			h[us[i]] = h[vs[i]] = i;
    		}
    		for (int i = 1; i <= n; i++) {
    			for (int j = i + 1; j <= n; j++) {
    				if (dfs(min(h[i], h[j]), i, j)) {
    					puts(ans + 1);
    					return;
    				}
    			}
    		}
    		puts("-1");
    	}
    
    }
    
    int main() {
    	scanf("%d%d%d", &n, &m, &type);
    	for (int i = 1; i <= m; i++) {
    		scanf("%d%d", us + i, vs + i);;
    	}
    	if (type == 1) {
    		subtask1::solve();
    	} else {
    		subtask2::solve();
    	}
    	return 0;
    }

    Problem F Histogram Rooks

      平方比立方好想,心情简单.jpg。

      考虑容斥,枚举哪些位置一定没有被覆盖,剩下的位置任意放置或不放置。

      朴素 dp 是建笛卡尔树,设 $f_{i, j, k}$ 表示考虑到第 $i$ 个点,硬点的位置一共占据了 $j$ 列,其中 $k$ 列已经填了。

      考虑一个点表示的矩形内的转移,转移有三种情况,一种是这一行什么都不干,一种是改变 $k$ 的值的情况下填数,一种是不会改变 $k$ 的转一下填数。

      写一下转移式子不难发现,第二种转移当且仅当 $k = 0$ 的时候转移系数非 0。因此只有 $j = k$ 或者 $k = 0$ 的状态有用。

      剩下的转移只有背包合并,它的复杂度和树上背包复杂度相同。

      所以总时间复杂度 $O(n^2)$

      三方做法好像是不容斥,考虑自底向上填,维护填上面的方案数,记录有多少列必须填,一些可能要填的数量,可以发现其中某个数量为 0。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    #define ll long long
    
    void exgcd(int a, int b, int& x, int& y) {
    	if (!b) {
    		x = 1, y = 0;
    	} else {
    		exgcd(b, a % b, y, x);
    		y -= (a / b) * x;
    	}
    }
    
    int inv(int a, int n) {
    	int x, y;
    	exgcd(a, n, x, y);
    	return (x < 0) ? (x + n) : (x);
    }
    
    const int Mod = 998244353;
    
    template <const int Mod = :: Mod>
    class Z {
    	public:
    		int v;
    
    		Z() : v(0) {	}
    		Z(int x) : v(x){	}
    		Z(ll x) : v(x % Mod) {	}
    
    		friend Z operator + (const Z& a, const Z& b) {
    			int x;
    			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
    		}
    		friend Z operator - (const Z& a, const Z& b) {
    			int x;
    			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
    		}
    		friend Z operator * (const Z& a, const Z& b) {
    			return Z(a.v * 1ll * b.v);
    		}
    		friend Z operator ~(const Z& a) {
    			return inv(a.v, Mod);
    		}
    		friend Z operator - (const Z& a) {
    			return Z(0) - a;
    		}
    		Z& operator += (Z b) {
    			return *this = *this + b;
    		}
    		Z& operator -= (Z b) {
    			return *this = *this - b;
    		}
    		Z& operator *= (Z b) {
    			return *this = *this * b;
    		}
    		friend boolean operator == (const Z& a, const Z& b) {
    			return a.v == b.v;
    		} 
    };
    
    Z<> qpow(Z<> a, int p) {
    	Z<> rt = Z<>(1), pa = a;
    	for ( ; p; p >>= 1, pa = pa * pa) {
    		if (p & 1) {
    			rt = rt * pa;
    		}
    	}
    	return rt;
    }
    
    typedef Z<> Zi;
    
    const int N = 805;
    
    int m, n;
    int h[N];
    Zi pw2[N * N];
    Zi f[N][N][2];
    Zi comb[N][N];
    int L[N], R[N];
    
    void prepare(int n) {
    	comb[0][0] = 1;
    	for (int i = 1; i <= n; i++) {
    		comb[i][0] = comb[i][i] = 1;
    		for (int j = 1; j < i; j++) {
    			comb[i][j] = comb[i - 1][j] + comb[i - 1][j - 1];
    		}
    	}
    	pw2[0] = 1;
    	for (int i = 1; i <= n * n + 3; i++) {
    		pw2[i] = pw2[i - 1] + pw2[i - 1];
    	}
    }
    
    int build(int l, int r, int bh) {
    	static Zi g[N][2];
    	int p = ++n;
    	L[p] = l, R[p] = r;
    	int mi = h[l];
    	for (int i = l; i <= r; i++) {
    		mi = min(mi, h[i]);
    	}
    	int ls = l - 1;
    	vector<int> son;
    	int cnew = 0;
    	for (int i = l; i <= r; i++) {
    		if (h[i] == mi) {
    			if (i - 1 >= ls + 1)
    				son.push_back(build(ls + 1, i - 1, mi));
    			ls = i, cnew++;
    		}
    	}
    	if (ls < r)
    		son.push_back(build(ls + 1, r, mi));
    	for (int i = 0; i <= cnew; i++)
    		f[p][i][i == 0] = comb[cnew][i];
    	int lenp = cnew;
    	for (auto e : son) {
    		int lene = R[e] - L[e] + 1;
    		for (int i = 0; i <= lenp + lene; i++)
    			g[i][0] = g[i][1] = 0;
    		for (int i = 0; i <= lenp; i++) {
    			for (int j = 0; j <= lene; j++) {
    				g[i + j][0] += f[p][i][0] * f[e][j][0];
    				g[i + j][1] += f[p][i][1] * f[e][j][1];
    			}
    		}
    		for (int i = 1; i <= lenp; i++) {
    			g[i][0] += f[p][i][0] * f[e][0][1];
    		}
    		for (int i = 1; i <= lene; i++) {
    			g[i][0] += f[e][i][0] * f[p][0][1];
    		}
    		lenp += lene;
    		for (int i = 0; i <= lenp; i++)
    			f[p][i][0] = g[i][0], f[p][i][1] = g[i][1];
    	}
    	int hei = mi - bh, len = r - l + 1;
    	for (int t = 1; t <= hei; t++) {
    		f[p][0][1] *= pw2[len];
    		for (int i = 1; i <= len; i++) {
    			Zi coef = (i & 1) ? (Mod - 1) : 1; 
    			f[p][i][1] = (f[p][i][1] * (pw2[len - i] - 1) + f[p][i][0] * coef);
    			f[p][i][0] *= pw2[len - i];
    		}
    	}
    /*	cerr << p << " [" << l << ", " << r << "]:
    ";
    	for (int i = 0; i <= len; i++)
    		cerr << f[p][i][0].v << " ";
    	cerr << '
    ';
    	for (int i = 0; i <= len; i++)
    		cerr << f[p][i][1].v << " ";
    	cerr << '
    '; */
    	return p;
    }
    
    int main() {
    	scanf("%d", &m);
    	for (int i = 1; i <= m; i++) {
    		scanf("%d", h + i);
    	}
    	prepare(m);
    	build(1, m, 0);
    	Zi ans = 0;
    	for (int i = 0; i <= m; i++)
    		ans += f[1][i][1];
    	printf("%d
    ", ans.v);
    	return 0;
    }

  • 相关阅读:
    Android客户端消息推送原理简介
    优秀程序员的十个习惯
    能让你成为更优秀程序员的10个C语言资源
    成大事必备9种能力、9种手段、9种心态
    33个优秀的HTML5应用演示 (转)
    Maven学习:Eclipse使用maven构建web项目(转)
    使用fiddler模拟http get
    TLS握手
    风暴英雄 http 302重定向 正在等待游戏模式下载完成
    page template in kentico
  • 原文地址:https://www.cnblogs.com/yyf0309/p/agc041.html
Copyright © 2011-2022 走看看