zoukankan      html  css  js  c++  java
  • HNCPC2019部分题解

    ProblemSet

    签到题就不写了。

    C. Distinct Substrings

    先对原串建出SAM,map存边。

    由于这题相当于添加一个字符再删除这个字符,添加下一个字符,所以每次都暴力跳后缀链接是复杂度是错的。

    (last) 向上跳的时候,遍历出边,把每个拥有数字 (c) 的出边的第一个点编号记下来,由于后缀自动机的边数是线性的,这样复杂度就是对的 (O(nlog n)) 虽然 (5e6) 但数据水还是跑过去了。

    #include <iostream>
    #include <map>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <fstream>
    
    typedef long long LL;
    typedef unsigned long long uLL;
    
    #define Int __int128
    #define SZ(x) ((int)x.size())
    #define ALL(x) (x).begin(), (x).end()
    #define MP(x, y) std::make_pair(x, y)
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    #define GO cerr << "GO" << endl;
    
    using namespace std;
    
    inline void proc_status()
    {
    	ifstream t("/proc/self/status");
    	cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>()) << endl;
    }
    
    template<class T> inline T read() 
    {
    	register int x = 0; register int f = 1; register char c;
    	while (!isdigit(c = getchar())) if (c == '-') f = -1;
    	while (x = (x << 1) + (x << 3) + (c xor 48), isdigit(c = getchar()));
    	return x * f;
    }
    
    template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
    template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
    
    const int maxN = (int) 1e6;
    const int mod = (int) 1e9 + 7;
    
    int pw[maxN + 2];
    int ncnt, last, n, m;
    
    struct Status
    {
    	int len, link;
    	map<int, int> ch;
    } st[2 * maxN];
    
    void init() { ncnt = 0, last = 0, st[0].len = 0, st[0].link = -1; }
    
    void insert(int c)
    {
    	int cur = ++ncnt;
    	int p = last;
    	st[cur].len = st[last].len + 1;
    	while (p != -1 and !st[p].ch.count(c))
    	{
    		st[p].ch[c] = cur;
    		p = st[p].link;
    	}
    	if (p == -1)
    		st[cur].link = 0;
    	else
    	{
    		int q = st[p].ch[c];
    		if (st[q].len == st[p].len + 1)
    			st[cur].link = q;
    		else 
    		{
    			int clone = ++ncnt;
    			st[clone] = st[q];
    			st[clone].len = st[p].len + 1;
    			while (p != -1 and st[p].ch[c] == q)
    			{
    				st[p].ch[c] = clone;
    				p = st[p].link;
    			}
    			st[cur].link = st[q].link = clone;
    		}
    	}
    	last = cur;
    }
    
    void Clear()
    {
    	for (register int i = 0; i <= ncnt; ++i) 
    		st[i].ch.clear(), st[i].len = st[i].link = 0;
    	ncnt = last = 0;
    }
    
    void Init() 
    {
    	pw[0] = 1;
    	for (register int i = 1; i <= m; ++i) pw[i] = 3ll * pw[i - 1] % mod;
    }
    
    void Solve()
    {
    	int ans = 0;
    
    	static int place[maxN + 2];
    
    	fill(place + 1, place + 1 + m, -1);
    
    	int L = st[last].len + 1, p = last;
    
    	while (p != -1)
    	{
    		for (map<int, int>::iterator it = st[p].ch.begin(); it != st[p].ch.end(); ++it)
    		{
    			if (place[it->first] < 0)
    				place[it->first] = p;
    		}
    		p = st[p].link;
    	}
    
    	for (register int i = 1; i <= m; ++i)
    	{
    		if (place[i] == -1)
    			ans ^= (LL) L * pw[i] % mod;
    		else 
    			ans ^= (LL) (L - st[place[i]].len - 1) * pw[i] % mod;
    	}
    
    	cout << ans << endl;
    }
    
    int main() 
    {
    #ifndef ONLINE_JUDGE
    	freopen("C.in", "r", stdin);
    	freopen("C.out", "w", stdout);
    #endif
    	while (scanf("%d%d", &n, &m) != EOF)
    	{
    		init();
    		Init();
    		for (register int i = 1, x; i <= n; ++i) 
    			x = read<int>(), insert(x);
    		Solve();
    		Clear();
    	}
    	return 0;
    }
    

    D. Modulo Nine

    (dp[i][j][k]) 表示填了前 (i) 个位置,最近的一个含3这个因子的数在 (k),次近的在 (j)的方案数。

    那么对每个点求出一个最近的需要满足限制的左端点(没有就默认为0),如果状态 (dp[i][j][k]) 满足 j和k都在i最近的左端点内,那么这个状态就是合法的,接下考虑i+1位填 ({0, 9})({3, 6})({1, 2, 4, 5, 7, 8}),刷表转移即可。

    Code

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    
    using namespace std;
    
    void chkmax(int &x, int y) { x < y ? x = y : 0; }
    
    const int maxN = 50;
    const int mod = (int) 1e9 + 7;
    
    int n, m;
    int L[maxN + 2];
    int dp[maxN + 2][maxN + 2][maxN + 2];
    
    void pls(int &x, int y) 
    {
    	x += y;
    	if (x >= mod) x -= mod;
    	if (x < 0) x += mod;
    }
    
    int main()
    {
    	freopen("D.in", "r", stdin);
    	freopen("D.out", "w", stdout);
    
    	while (scanf("%d%d", &n, &m) != EOF)
    	{
    		memset(L, 0, sizeof L);
    		for (int i = 1; i <= m; ++i)
    		{
    			int l, r;
    			scanf("%d%d", &l, &r);
    			chkmax(L[r], l);
    		}
    		memset(dp, 0, sizeof dp);
    		dp[0][0][0] = 1;
    		for (int i = 0; i < n; ++i)
    			for (int j = 0; j <= i; ++j)
    				for (int k = j; k <= i; ++k)
    				{
    					if (!dp[i][j][k]) continue;
    					if (L[i] <= j and L[i] <= k)
    					{
    						int curr = dp[i][j][k];
    						// {1, 2, 4, 5, 7, 8}
    						pls(dp[i + 1][j][k], 6ll * curr % mod); 
    						// {3, 6}
    						pls(dp[i + 1][k][i + 1], 2ll * curr % mod);
    						// {0, 9}
    						pls(dp[i + 1][i + 1][i + 1], 2ll * curr % mod);
    					}
    				}
    		int ans = 0;
    		for (int i = L[n]; i <= n; ++i)
    			for (int j = i; j <= n; ++j)
    				pls(ans, dp[n][i][j]);
    		printf("%d
    ", ans);
    	}
    }
    

    G. 字典序

    参考了zsy的题解:

    不难发现只要考虑相邻两行的限制,如果满足任意相邻两行满足,那么就是合法的,即 (forall iin[1, n - 1],jin[2,m],exists k < j) 满足 (a[i][j] > a[i + 1][j])(a[i][k] < a[i + 1][k])

    这个限制可以抽象成 (1le ile n - 1)({1,2cdots m}) 的两个子集 (A[i])(B[i]),需要满足最终的排列中 (B[i]) 中的每一个元素都在至少一个 (A[i]) 中元素的后面。

    那么每个点(列)会有些限制,这些限制的个数就是被多少个B集合包含,因为B集合是被A集合限制的,当限制个数为0时,我们就可以把它加入到排列中了。

    4 3 3
    1 5 1
    1 5 1
    3 5 2
    

    比如上面这个:(A[1] = {2},A[2] = {}, A[3]={1,3}), (B[1] = {1, 3}, B[2] = {}, B[3] = {})

    我们从前往后构造排列,若构造了 (A[x]) 中的数,那么对应的 (B[x]) 所包含的列的限制就会-1,所以每次找到最小的限制数为0的列,作为当前构造排列的最后一个,然后把它所在 (A[x]) 集合对应的 (B[x]) 集合中的列的限制-1,再将 (A[x])(B[x]) 删掉。

    Code

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <vector>
    
    using namespace std;
    
    const int maxN = 2000;
    
    int n, m, a[maxN + 2][maxN + 2];
    int limits[maxN + 2];
    vector<int> B_to_Lie[maxN + 2];
    vector<int> Lie_to_A[maxN + 2];
    
    int main()
    {
    	freopen("G.in", "r", stdin);
    	freopen("G.out", "w", stdout);
    
    	while (scanf("%d%d", &n, &m) != EOF)
    	{
    		memset(limits, 0, sizeof limits);
    		for (int i = 1; i < n; ++i) B_to_Lie[i].clear();
    		for (int i = 1; i <= m; ++i) Lie_to_A[i].clear();
    
    		for (int i = 1; i <= n; ++i)
    			for (int j = 1; j <= m; ++j)
    				scanf("%d", &a[i][j]);
    
    		for (int i = 1; i < n; ++i)
    			for (int j = 1; j <= m; ++j)
    				if (a[i][j] < a[i + 1][j])
    					Lie_to_A[j].push_back(i);
    				else if (a[i][j] > a[i + 1][j])
    				{
    					limits[j]++;
    					B_to_Lie[i].push_back(j);
    				}
    
    		int tot = 0;
    		static int ans[maxN + 2];
    		
    		for (int t = 1; t <= m; ++t)
    		{
    			int p = 0;
    			for (int i = 1; i <= m; ++i)
    				if (!limits[i])
    				{
    					p = i;
    					limits[i] = 0x3f3f3f3f;
    					break;
    				}
    			if (!p)
    				break;
    			ans[++tot] = p;
    			for (int i = 0; i < (int) Lie_to_A[p].size(); ++i)
    			{
    				int A = Lie_to_A[p][i];
    				for (int j = 0; j < (int) B_to_Lie[A].size(); ++j)
    				{
    					int Lie = B_to_Lie[A][j];
    					limits[Lie]--;
    				}
    				B_to_Lie[A].clear();
    			}
    			Lie_to_A[p].clear();
    		}
    		if (tot == m)
    			for (int i = 1; i <= m; ++i)
    				if (i != m) printf("%d ", ans[i]);
    				else printf("%d
    ", ans[i]);
    		else 
    			puts("-1");
    	}
    	return 0;
    }
    

    H. 有向图

    (E[u]) 为走无限次后期望经过 (u) 多少次,由于经过n+1...n+m的次数为1,所以此时n+1...n+m点期望就是概率。

    列出方程高消即可。

    Code

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    
    #define LL long long
    
    using namespace std;
    
    const int maxN = 500;
    const int mod = (int) 1e9 + 7;
    
    LL qpow(LL a, LL b)
    {
    	LL ans = 1;
    	while (b) 
    	{
    		if (b & 1)
    			ans = ans * a % mod;
    		a = a * a % mod;
    		b >>= 1;
    	}
    	return ans;
    }
    
    int n, m;
    int a[maxN + 2][maxN + 2], P[maxN + 2][maxN + 2];
    
    int main()
    {
    	freopen("H.in", "r", stdin);
    	freopen("H.out", "w", stdout);
    	while (scanf("%d%d", &n, &m) != EOF)
    	{
    		memset(a, 0, sizeof a);
    		memset(P, 0, sizeof P);
    		int inv = qpow(10000, mod - 2);
    		for (int i = 1; i <= n; ++i)
    			for (int j = 1; j <= n + m; ++j)
    			{
    				scanf("%d", &P[i][j]);
    				P[i][j] = 1ll * P[i][j] * inv % mod;
    			}
    		for (int i = 1; i <= n; ++i)
    		{
    			for (int j = 1; j <= n; ++j)
    			{
    				a[i][j] = P[j][i];
    				if (i == j) a[i][i]--;
    				(a[i][j] += mod) %= mod;
    			}
    			if (i == 1) a[1][n + 1] = mod - 1;
    			else a[i][n + 1] = 0;
    		}
    		for (int i = 1; i <= n; ++i)
    		{
    			for (int j = 1; j <= n; ++j)
    				if (i != j)
    				{
    					int mul = 1ll * a[j][i] * qpow(a[i][i], mod - 2) % mod;
    					for (int k = 1; k <= n + 1; ++k)
    					{
    						a[j][k] -= 1ll * a[i][k] * mul % mod;
    						a[j][k] %= mod;
    						(a[j][k] += mod) %= mod;
    					}
    				}
    		}
    		for (int i = 1; i <= n; ++i)
    			a[i][n + 1] = (1ll * a[i][n + 1] * qpow(a[i][i], mod - 2) % mod + mod) % mod;
    	   	for (int i = 1 + n; i <= n + m; ++i)
    		{
    			int ans = 0;
    			for (int j = 1; j <= n; ++j)
    				(ans += 1ll * a[j][n + 1] * P[j][i] % mod) %= mod;
    			printf("%d", (ans + mod) % mod);
    			if (i != n + m) 
    				putchar(' ');
    		}
    		putchar('
    ');
    	}
    }
    

    J. Parity of Tuples (Easy)

    首先对每一行单独考虑贡献再加起来。

    接下来都是对一行考虑:

    由于要求and x后二进制下都是奇数个1,所以设 (f[i][S]) 为考虑了 (x) 的前i位,这一行每个数二进制下1个数的奇偶性二进制状态为S对答案的贡献。

    对于 (3^x) 将x拆成二进制相加,转移时乘上即可。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    const int maxN = (int) 1e4, maxM = 30, maxK = 30;
    const int mod = (int) 1e9 + 7;
    
    int n, m, k;
    int b[maxK + 2], pw[maxK + 2];
    int a[maxN + 2][maxM + 2];
    int f[maxK + 2][1 << maxM];
    
    int main()
    {
    	freopen("J.in", "r", stdin);
    	freopen("J.out", "w", stdout);
    	while (scanf("%d%d%d", &n, &m, &k) != EOF)
    	{
    		for (int i = 1; i <= n; ++i)
    			for (int j = 0; j < m; ++j)
    				scanf("%d", &a[i][j]);
    		int ans = 0;
    		for (int t = 1; t <= n; ++t)
    		{
    			for (int i = 0; i <= k; ++i)
    			{
    				b[i] = 0;
    				for (int j = 0; j < m; ++j)
    					b[i] |= (a[t][j] >> i & 1) << j;
    			}
    			pw[0] = 3;
    			for (int i = 1; i <= k; ++i)
    				pw[i] = 1ll * pw[i - 1] * pw[i - 1] % mod;
    			memset(f, 0, sizeof f);
    			f[0][0] = 1;
    			for (int i = 0; i < k; ++i)
    				for (int S = 0; S < (1 << m); ++S)
    					if (f[i][S])
    					{
    						(f[i + 1][S ^ b[i]] += 1ll * f[i][S] * pw[i]) %= mod;
    						(f[i + 1][S] += f[i][S]) %= mod;
    					}
    			(ans += f[k][(1 << m) - 1]) %= mod;
    		}
    		printf("%d
    ", (ans + mod) % mod);
    	}
    }
    

    K. 双向链表练习题

    deque 启发式合并。

    Code

    #include <iostream>
    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <fstream>
    
    typedef long long LL;
    typedef unsigned long long uLL;
    
    #define SZ(x) ((int)x.size())
    #define ALL(x) (x).begin(), (x).end()
    #define MP(x, y) std::make_pair(x, y)
    #define DE(x) cerr << x << endl;
    #define debug(...) fprintf(stderr, __VA_ARGS__)
    #define GO cerr << "GO" << endl;
    #define rep(i, a, b) for (register int (i) = (a); (i) <= (b); ++(i))
    
    using namespace std;
    
    inline void proc_status()
    {
    	ifstream t("/proc/self/status");
    	cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>()) << endl;
    }
    inline int read() 
    {
    	register int x = 0; register int f = 1; register char c;
    	while (!isdigit(c = getchar())) if (c == '-') f = -1;
    	while (x = (x << 1) + (x << 3) + (c xor 48), isdigit(c = getchar()));
    	return x * f;
    }
    template<class T> inline void write(T x) 
    {
    	static char stk[30]; static int top = 0;
    	if (x < 0) { x = -x, putchar('-'); }
    	while (stk[++top] = x % 10 xor 48, x /= 10, x);
    	while (putchar(stk[top--]), top);
    }
    template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
    template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
    
    const int maxN = 2e5;
    
    deque<int> q[maxN + 2];
    int n, m;
    bool rev[maxN + 2];
    
    int size(int x) { return q[x].size(); }
    
    int front(int x)
    {
    	if (rev[x]) return q[x].back();
    	else return q[x].front();
    }
    
    int back(int x)
    {
    	if (rev[x]) return q[x].front();
    	else return q[x].back();
    }
    
    void push_front(int x, int y)
    {
    	if (rev[x]) q[x].push_back(y);
    	else q[x].push_front(y);
    }
    
    void push_back(int x, int y)
    {
    	if (rev[x]) q[x].push_front(y);
    	else q[x].push_back(y);
    }
    
    void pop_back(int x)
    {
    	if (rev[x]) q[x].pop_front();
    	else q[x].pop_back();
    }
    
    void pop_front(int x)
    {
    	if (rev[x]) q[x].pop_back();
    	else q[x].pop_front();
    }
    
    int main() 
    {
    #ifndef ONLINE_JUDGE
    	freopen("K.in", "r", stdin);
    	freopen("K.out", "w", stdout);
    #endif
    	while (scanf("%d%d", &n, &m) != EOF)
    	{
    		for (int i = 1; i <= n; ++i)
    			q[i].clear(), q[i].push_front(i), rev[i] = 0;
    		for (int i = 1; i <= m; ++i)
    		{
    			int a, b;
    			scanf("%d%d", &a, &b);
    			if (q[a].size() > q[b].size())
    			{
    				while (size(b))
    				{
    					push_back(a, front(b));
    					pop_front(b);
    				}
    				rev[a] ^= 1;
    			} else 
    			{
    				while (size(a))
    				{
    					push_front(b, back(a));
    					pop_back(a);
    				}
    				rev[a] = rev[b] ^ 1;
    				swap(q[a], q[b]);
    			}
    		}
    		printf("%d", q[1].size());
    		if (rev[1]) { while (q[1].size()) printf(" %d", q[1].back()), q[1].pop_back(); }
    		else { while (q[1].size()) printf(" %d", q[1].front()), q[1].pop_front(); }
    		puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    英文字母打字速度测试游戏代码
    JS写一个JS解释器
    JS中try.. catch..的用法
    使用HTML CSS和JavaScript创建图像动画
    6个强大的CSS选择器
    TypeScript 3.9稳定版本新增功能
    10个JavaScript代码片段,使你更加容易前端开发。
    BZOJ.3811.玛里苟斯(线性基)
    Bluestein's Algorithm
    AGC 002E.Candy Piles(博弈论)
  • 原文地址:https://www.cnblogs.com/cnyali-Tea/p/11443455.html
Copyright © 2011-2022 走看看