大二上第七周学习笔记

恢复疯狂训练模式

周一

C. Watto and Mechanism(字典树+dfs)

首先如果给了一堆串,然后问你一个串在不在里面,就直接用字典树就可以了

这道题不同的是要有一个位置不相同

那就修改一下,dfs就可以了

在dfs的过程中,每次都判断能否改变一个字母继续走就可以了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 6e5 + 10;
int t[N][3], End[N], n, m, cnt;
string str;

void add(string s)
{
	int p = 0, len = s.size();
	rep(i, 0, len)
	{
		if(!t[p][s[i] - 'a']) t[p][s[i] - 'a'] = ++cnt;
		p = t[p][s[i] - 'a'];
	}
	End[p] = 1;
}

bool dfs(int pos, int p, int flag)
{
	if(pos == str.size()) return flag && End[p];
	if(t[p][str[pos] - 'a'] && dfs(pos + 1, t[p][str[pos] - 'a'], flag)) return true;

	if(!flag)
	{
		rep(j, 0, 3)
			if(str[pos] - 'a' != j && t[p][j] && dfs(pos + 1, t[p][j], 1))
				return true;
	}

	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n)
	{
		string s;
		cin >> s;
		add(s);
	}
	_for(i, 1, m)
	{
		cin >> str;
		puts(dfs(0, 0, 0) ? "YES" : "NO");
	}

    return 0;
}

C. Watto and Mechanism(哈希)

这道题有个很关键的信息,就是字符从长度不超过6e5

对于看以前的字符是否存在,可以用哈希来解决

所以我们就可以枚举每一个字母,O(1)计算出新的哈希值,然后判断即可

这看起来很慢,但是题目告诉我们不超过6e5 是可以做的

这道题卡哈希卡到我怀疑人生…… 双哈希也WA

一直过不去……

最后看了一个AC的代码,在取模上和我不太一样,过了

不纠结了……

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
typedef pair<ull, ull> pa;
const ull mod1 = 1e9 + 7, mod2 = 1e9 + 9;
const int N = 6e5 + 10, base = 131;
map<pa, bool> mp;
ull p1[N], p2[N];
int n, m;

pa get_hash(string s)
{
	int len = s.size();
	ull res1 = 0, res2 = 0;
	rep(i, 0, len) 
	{
		res1 = (res1 * base + s[i]) % mod1;
		res2 = (res2 * base + s[i]) % mod2;
	}
	return make_pair(res1, res2);
}

int main()
{
	p1[0] = 1; rep(i, 1, N) p1[i] = p1[i - 1] * base % mod1;
	p2[0] = 1; rep(i, 1, N) p2[i] = p2[i - 1] * base % mod2;

	scanf("%d%d", &n, &m);
	_for(i, 1, n)
	{
		string s;
		cin >> s;
		mp[get_hash(s)] = 1;
	}
	_for(t, 1, m)
	{
		string s;
		cin >> s;

		pa val = get_hash(s);
		int len = s.size(), find = 0;
		rep(i, 0, len)
		{
			for(int k = 'a'; k <= 'c'; k++)
				if(k != s[i])
				{
					ull cur1 = (val.first + (k - s[i]) * p1[len - i - 1] + 3 * mod1) % mod1;
					ull cur2 = (val.second + (k - s[i]) * p2[len - i - 1] + 3 * mod2) % mod2;
					if(mp[make_pair(cur1, cur2)])
					{
						find = 1;
						break;
					}
				}
			if(find) break;
		}
		puts(find ? "YES" : "NO");
	}

    return 0;
}

E. XOR on Segment(异或+线段树)

首先这种位运算常见的思路就是拆成一位一位来考虑

所以开20个线段树就可以了

有两个地方注意一下

一个是修改的时候懒标记也要下传

一个标记会叠加,要考虑进去

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int t[N << 4][20], lazy[N << 4][20], n, m;

void up(int k, int id)
{
	t[k][id] = t[l(k)][id] + t[r(k)][id];
}

void add(int k, int l, int r, int x, int id)
{
	if(l == r)
	{
		t[k][id]++;
		return;
	}
	int m = l + r >> 1;
	if(x <= m) add(l(k), l, m, x, id);
	else add(r(k), m + 1, r, x, id);
	up(k, id);
}

void update(int k, int l, int r, int id)
{
	t[k][id] = (r - l + 1) - t[k][id];
	lazy[k][id] ^= 1;
}

void down(int k, int l, int r, int id)
{
	if(lazy[k][id])
	{
		int m = l + r >> 1;
		update(l(k), l, m, id);
		update(r(k), m + 1, r, id);
		lazy[k][id] = 0;
	}
}

void change(int k, int l, int r, int L, int R, int id)
{
	if(L <= l && r <= R)
	{
		update(k, l, r, id);
		return;
	}
	down(k, l, r, id);
	int m = l + r >> 1;
	if(L <= m) change(l(k), l, m, L, R, id);
	if(R > m) change(r(k), m + 1, r, L, R, id);
	up(k, id);
}

int ask(int k, int l, int r, int L, int R, int id)
{
	if(L <= l && r <= R) return t[k][id];
	down(k, l, r, id);
	int m = l + r >> 1, res = 0;
	if(L <= m) res += ask(l(k), l, m, L, R, id);
	if(R > m) res += ask(r(k), m + 1, r, L, R, id);
	return res;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
	{
		int x; scanf("%d", &x);
		_for(j, 0, 19)	
			if(x & (1 << j))
				add(1, 1, n, i, j);	
	}

	scanf("%d", &m);
	while(m--)
	{
		int t, l, r, x;
		scanf("%d%d%d", &t, &l, &r);
		if(t == 1)
		{
			ll ans = 0;
			_for(j, 0, 19)
				ans += ask(1, 1, n, l, r, j) * (1LL << j);
			printf("%lld\n", ans);
		}
		else
		{
			scanf("%d", &x);
			_for(j, 0, 19)	
				if(x & (1 << j))
					change(1, 1, n, l, r, j);
		}
	}

    return 0;
}

A. Windblume Ode(质数)

这道题稍微想复杂了一点

涉及到质数合数,可以从奇偶来考虑

大于2的偶数一定是合数

由于n >= 3所以和一定大于2

如果和是质数,那么其一定是奇数

这时只要再减去一个奇数就是偶数,也就是合数了,这个数不可能为2

因为和为奇数,所以肯定有一个奇数

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 100 + 10;
int a[N], n;

bool check(int x)
{
	if(x == 0 || x == 1) return false;
	for(int i = 2; i * i <= x; i++)
		if(x % i == 0)
			return false;
	return true;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		int sum = 0;
		_for(i, 1, n) scanf("%d", &a[i]), sum += a[i];

		if(!check(sum))
		{
			printf("%d\n", n);
			_for(i, 1, n) printf("%d ", i);
			puts("");
		}
		else
		{
			int t;
			_for(i, 1, n)
				if(!check(sum - a[i]))
				{
					t = i;
					break;
				}
			printf("%d\n", n - 1);
			_for(i, 1, n) 
				if(i != t)
					printf("%d ", i);
			puts("");
		}
	}

    return 0;
}

E1. Weights Division (easy version)(简化问题)

遇到树的问题,一个常见的思路是简化成一条链来思考,然后拓展到树中

如果是一条链,显然贪心,每次都能减少的最大即可

减少的量是n/2向上取整

所以可以用优秀队列

当为一个树中,发现一条边可以影响多个路径

所以减少的量就乘以路径数就可以了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
struct node{ int v, w; };
struct Edge
{
	int w, cnt;
	bool operator < (const Edge& rhs) const
	{
		return (w + 1) / 2LL * cnt < (rhs.w + 1) / 2LL * rhs.cnt;
	}
};
vector<node> g[N];
ll S, sum;
int dp[N], f[N], n;

void dfs(int u, int fa, int w, ll weight)
{
	f[u] = w;
	dp[u] = 0;
	if(g[u].size() == 1) 
	{
		dp[u] = 1;
		sum += weight;
	}
	for(auto x: g[u])
	{
		int v = x.v;  w = x.w;
		if(v == fa) continue;
		dfs(v, u, w, weight + w);
		dp[u] += dp[v];
	}
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%lld", &n, &S);
		_for(i, 1, n) g[i].clear();
		_for(i, 1, n - 1)
		{
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			g[u].push_back(node{v, w});
			g[v].push_back(node{u, w});
		}

		sum = 0;
		dfs(1, 0, 0, 0);
		priority_queue<Edge> q;
		_for(i, 1, n) q.push(Edge{f[i], dp[i]});

		int ans = 0;
		while(sum > S)
		{
			Edge x = q.top(); q.pop();
			sum -= (x.w + 1) / 2LL * x.cnt;
			q.push(Edge{x.w / 2, x.cnt});
			ans++;
		}
		printf("%d\n", ans);
	}

    return 0;
}

B. Make Them Equal(构造)

这题差一点一点

已经想到了i=1时非常特殊

所以尽可能一移a1上,然后再用a1给其他的数加

问题是我移的时候是对i取模移的,导致a1不够大,可能出现负数的情况

正解是移到极致,直接2到n全部变成0

不是i的倍数的时候就用a1加到i的倍数

这样a1是一定不会为负数的

因为a >= 1

所以每次需要的数是i - ai % i < i

而1 ~ i - 1最少的和都为i - 1

太妙了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e4 + 10;
int a[N], n, sum;
struct node{ int i, j, x; };
vector<node> ans;

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		sum = 0;
		ans.clear();

		scanf("%d", &n);
		_for(i, 1, n)
		{
			scanf("%d", &a[i]);
			sum += a[i];
		}
		if(sum % n != 0)
		{
			puts("-1");
			continue;
		}

		int k = sum / n, ok = 1;
		_for(i, 2, n)
		{
			if(a[i] % i != 0)
			{
				int t = i - a[i] % i;
				a[i] += t;
				a[1] -= t;
				ans.push_back(node{1, i, t});
			}
			ans.push_back(node{i, 1, a[i] / i});
			a[1] += a[i];
			a[i] = 0;
		}
		_for(i, 2, n) ans.push_back(node{1, i, k});
		
		printf("%d\n", ans.size());
		for(auto t: ans) printf("%d %d %d\n", t.i, t.j, t.x);
	}

    return 0;
}

E. Modular Stability(数学)

不要畏惧数论,其实也没有多难

我首先发现模数里面有1就可以了

于是我看那个7 3的16是怎么算的

1一定选的话有15种,还差一种什么呢

灵光一现貌似成倍数的都可以 这差的一组是2 4 6

2 4 6显然可以看成2 乘以 1 2 3

于是可以枚举选择的末尾位置,在中间选一定个数的方案用组合数,这一串可以乘一些数

只要满足最大的数不超过n就行了,所以可以算出可以乘的数是1到n/i

组合数的话预处理出阶乘就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
const int mod = 998244353;
int fac[N], n, k, ans;

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int binpow(int a, int b)
{
	int res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = mul(res, a);
		a = mul(a, a);
	}
	return res;
}

int inv(int x) { return binpow(x, mod - 2); }

int C(int m, int n)
{
	return mul(fac[n], mul(inv(fac[m]), inv(fac[n - m])));
}

int main()
{
	fac[0] = 1;
	rep(i, 1, N) fac[i] = mul(fac[i - 1], i);

	scanf("%d%d", &n, &k);
	if(k == 1) printf("%d\n", n);
	else
	{
		_for(i, k, n) ans = add(ans, mul(C(k - 2, i - 2), n / i));
		printf("%d\n", ans);
	}

    return 0;
}

周二

B. Domino for Young(二分图匹配 / 黑白染色)

这道题可太妙了

用到了棋盘的黑白染色,之前做过几道cf题也用到了黑白染色

黑白染色后,每次都是一个白与一个黑相连

这时最多的个数就是黑白个数的最小值

证明可以用二分图匹配的思想

比如对于一个白子,它可以和周围四个黑子匹配

如果都已经匹配,那么一定可以通过找增广路找到匹配

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
ll black, white;
int n;

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
	{
		int x; scanf("%d", &x);
		if(i % 2 == 1)
		{
			black += (x + 1) / 2;
			white += x / 2;
		}
		else
		{
			white += (x + 1) / 2;
			black += x / 2;
		}
	}
	printf("%lld\n", min(white, black));

    return 0;
}

E. Reachability from the Capital(缩点+度数)

思考一段时间发现,把起点可以到达的点排除之后,寻找有多少个入度为0的点

但是我发现有环,这时入度就不为0了

所以我就用tarjan缩点,这样就没有环了

这就是题解里说的O(n + m)的做法

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 5e3 + 10;
int f[N], dfn[N], low[N], st[N], co[N], vis[N], in[N]; 
int top, cnt, n, m, s, id;
vector<int> g[N], G[N];
vector<pair<int, int>> Edge;
 
void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
 
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!co[v]) low[u] = min(low[u], dfn[v]);
	}
 
	if(low[u] == dfn[u])
	{
		id++;
		while(1)
		{
			co[st[top--]] = id;
			if(st[top + 1] == u) break;
		}
	}
}
 
void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	s = co[s];
 
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
				Edge.push_back(make_pair(co[u], co[v]));
	
	sort(Edge.begin(), Edge.end());
	int t = unique(Edge.begin(), Edge.end()) - Edge.begin();
	rep(i, 0, t) G[Edge[i].first].push_back(Edge[i].second);
}
 
void dfs2(int u)
{
	vis[u] = 1;
	for(int v: G[u])
		if(!vis[v])
			dfs2(v);
}
 
int main()
{
	scanf("%d%d%d", &n, &m, &s);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
 
	build();
	dfs2(s);
 
	_for(u, 1, id)
		if(!vis[u])
			for(int v: G[u])
				if(!vis[v])
					in[v]++;
					
	int ans = 0;
	_for(i, 1, id)
		if(!vis[i] && !in[i])
			ans++;
	printf("%d\n", ans);
 
    return 0;
}

但其实搞复杂了

我没注意到这道题的数据范围比较小

n是5000而不是以往的1e5级别的

这个数据范围可以暴力

于是可以暴力算出每个点能到达的节点个数,排序

然后从大到小dfs,这样一定是最优的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 5e3 + 10;
int vis[N], k[N], cnt[N], n, m, s, sum;
vector<pair<int, int>> ve;
vector<int> g[N];

void dfs(int u)
{
	vis[u] = 1;
	for(int v: g[u])
		if(!vis[v])
			dfs(v);
}

void dfs2(int u)
{
	sum++;
	k[u] = 1;
	for(int v: g[u])
		if(!k[v] && !vis[v])
			dfs2(v);
}
 
int main()
{
	scanf("%d%d%d", &n, &m, &s);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
 
	dfs(s);
	_for(i, 1, n)
		if(!vis[i])
		{
			sum = 0;
			memset(k, 0, sizeof k);
			dfs2(i);
			ve.push_back(make_pair(-sum, i));
		}

	int ans = 0;
	sort(ve.begin(), ve.end());
	for(auto x: ve)
	{
		int u = x.second;
		if(vis[u]) continue;
		dfs(u);
		ans++;
	}
	printf("%d\n", ans);
 
    return 0;
}

周五

最近在打往年区域赛

每一场都好好补题,区域赛的题目质量还是挺高的

昨天打了2020年ICPC南京区域赛

A. Ah, It’s Yesterday Once More(构造)

队友想到了蛇形的构造,但是是从上到下的

正解是斜的蛇形构造

思路是尽可能的利用格子,构造长的绕的道路

代码不放了,构造斜着蛇形就好了

K. K Co-prime Permutation(互质)

套路是相邻的数质因数为1

首先1和任何数gcd都为1,所以至少有一个,k=0就输出-1

其他情况,前k个移个位置使得gcd为1,后面的数与自己gcd不为1

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	if(k == 0) puts("-1");
	else
	{
		vector<int> ans;
		_for(i, 1, k)
		{
			int t = i - 1;
			if(t == 0) t = k;
			ans.push_back(t);
		}
		_for(i, k + 1, n) ans.push_back(i);

		rep(i, 0, ans.size())
		{
			printf("%d", ans[i]);
			if(i != ans.size() - 1) putchar(' ');
		}
	}
 
    return 0;
}

L. Let's Play Curling(思维)

离散化一下,然后找最长的连续的红球就行了

写的时候注意红球位置会重复

我开始两发离散化的数组要开了两倍,但我的另外两个数组忘记开两倍了……

吸取教训,以后最好N就开两倍,就不用判断哪些要开两倍,哪些不用开了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int a[N], b[N], lsh[N], k[N], val[N], n, m, cnt;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        cnt = 0;
        scanf("%d%d", &n, &m);
        _for(i, 1, n) scanf("%d", &a[i]), lsh[++cnt] = a[i];
        _for(i, 1, m) scanf("%d", &b[i]), lsh[++cnt] = b[i];

        sort(lsh + 1, lsh + cnt + 1);
        cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
        _for(i, 1, cnt) k[i] = 0, val[i] = 0;

        _for(i, 1, n)
        {
            int cur = lower_bound(lsh + 1, lsh + cnt + 1, a[i]) - lsh;
            val[cur]++;
        }
        _for(i, 1, m)
        {
            int cur = lower_bound(lsh + 1, lsh + cnt + 1, b[i]) - lsh;
            k[cur] = 1;
        }

        int ans = 0, s = 0;
        _for(i, 1, cnt)
        {
            if(!k[i]) s += val[i];
            else
            {
                ans = max(ans, s);
                s = 0;
            }
        }
		ans = max(ans, s);
		if(!ans) puts("Impossible");
        else printf("%d\n", ans);
    }

    return 0;
}

E. Evil Coordinate(构造)

考试的时候是分类讨论,写了150行,然后WA了

弄得很复杂,我也不知道哪些地方漏了

之后队友自己分类讨论了一波过了

看了题解,挺妙的

如果有解,那么一定可以通过同一种方向排列到一起实现

所以就枚举所有情况 4!= 24

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int d[10], p[10], xx, yy;
int dir[4][2] = {0, 1, 0, -1, -1, 0, 1, 0};
char str[] = {'U', 'D', 'L', 'R'};

bool check()
{
	int x = 0, y = 0;
	rep(i, 0, 4)
	{
		int id = p[i];
		_for(j, 1, d[id])
		{
			x += dir[id][0], y += dir[id][1];
			if(x == xx && y == yy) return false;
		}
	}
	return true;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
		string s;
		cin >> xx >> yy >> s;
		int len = s.size();

		if(!xx && !yy)
		{
			puts("Impossible");
			continue;
		}

		memset(d, 0, sizeof d);
		rep(i, 0, len)
			rep(j, 0, 4)
				if(s[i] == str[j])
					d[j]++;

		int ok = 0;
		rep(i, 0, 4) p[i] = i;
		while(1)
		{
			if(check()) { ok = 1; break; }
			if(!next_permutation(p, p + 4)) break;
		}

		if(!ok) puts("Impossible");
		else
		{
			rep(i, 0, 4)
				_for(j, 1, d[p[i]])
					putchar(str[p[i]]);
			puts("");
		}
	}	

    return 0;
}

H. Harmonious Rectangle(抽屉原理)

这题挺可惜的

当时我想的时候感觉特别容易满足,但没有深入去想

其实超过一定情况是一定满足的

相等可以看作一个二元组相等

而二元组是9种情况

也就是说两行里面有超过10列,那么一定有一对二元组相等,一定满足条件

所以数据大的时候是一定满足的

数据小的时候直接暴力打表

方式是dfs,找到一个答案就剩下随便填

打表代码

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7;
int a[15][15], n, m;

ll binpow(ll a, ll b)
{
	ll res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}

bool check(int x2, int y2)
{
	for(int x1 = 1; x1 < x2; x1++)
		for(int y1 = 1; y1 < y2; y1++)
			if(a[x1][y1] == a[x1][y2] && a[x2][y1] == a[x2][y2] ||
				a[x1][y1] == a[x2][y1] && a[x1][y2] == a[x2][y2] )
					return true;
	return false;
}

ll dfs(int x2, int y2, int cnt)
{
	if(y2 == m + 1) y2 = 1, x2++;
	if(x2 > n) return 0;
	ll res = 0;
	_for(c, 1, 3)
	{
		a[x2][y2] = c;
		if(check(x2, y2)) res = (res + binpow(3, cnt)) % mod;
		else res = (res + dfs(x2, y2 + 1, cnt - 1)) % mod;
	}
	return res;
}

int main()
{
	for(n = 1; n <= 9; n++)
		for(m = 1; m <= 9; m++)
		{
			memset(a, 0, sizeof a);
			if(n == 1 || m == 1) printf("%d, ", 0);
			else printf("%lld, ", dfs(1, 1, n * m - 1));
		}

    return 0;
}

AC代码

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7;
int ans[9][9] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489, 
0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612, 0, 4761, 518265, 43022385, 486780060, 
429534507, 792294829, 175880701, 246336683, 0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 
644897553, 0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848, 0, 4767849, 460338013, 
792294829, 748778899, 579440654, 236701429, 666021604, 589237756, 0, 43046721, 429534507, 175880701, 953271190, 412233812, 
666021604, 767713261, 966670169, 0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245};

ll binpow(ll a, ll b)
{
	ll res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}


int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		int n, m;
		scanf("%d%d", &n, &m);
		if(n == 1 || m == 1) puts("0");
		else if(n <= 9 && m <= 9) printf("%d\n", ans[n - 1][m - 1]);
		else printf("%d\n", binpow(3, n * m));
	}

    return 0;
}

M. Monster Hunter(树上背包)

好久没写树上背包了

这题要简化一下

一个点的权值是自己点加上儿子中活着的点

儿子对当前有贡献当且仅当儿子和自己都活着的时候

可以用dp[0/1][i][j]

第一维表示是否活着

第二维表示节点

第三维表示当前子树中有几个活着的点

这个活着点就有点背包中重量的味道了,于是可以写树上背包

背包九讲里面讲到了泛化背包,看作一个函数的思想很有帮助,就涉及到背包的本质了

枚举的时候有小技巧,不然会T,具体看代码

这样枚举可以做到n方的时间复杂度

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e3 + 10;
ll dp[2][N][N];
vector<int> g[N];
int siz[N], n;

void dfs(int u)
{
	siz[u] = 1;
	for(int v: g[u])
	{
		dfs(v);
		for(int j = siz[u]; j >= 0; j--)    //注意枚举重量是逆序枚举
			_for(k, 0, siz[v])
			{
				dp[0][u][j + k] = min(dp[0][u][j + k], dp[0][u][j] + min(dp[0][v][k], dp[1][v][k]));  //为了加速,这里用j + k
				dp[1][u][j + k] = min(dp[1][u][j + k], dp[1][u][j] + min(dp[0][v][k], dp[1][v][k] + dp[1][v][1]));
			}
		siz[u] += siz[v];
	}
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		_for(i, 0, n)
			_for(j, 0, n)
				dp[0][i][j] = dp[1][i][j] = 1e18; //long long的最大值设为1e18 int为1e9
		_for(i, 1, n) g[i].clear();

		_for(i, 2, n)
		{
			int x; scanf("%d", &x);
			g[x].push_back(i);
		}
		_for(i, 1, n) 
		{
			scanf("%lld", &dp[1][i][1]); //初始化
			dp[0][i][0] = 0;             
		}
		dfs(1);
		for(int i = n; i >= 0; i--) printf("%lld ", min(dp[1][1][i], dp[0][1][i])); puts(""); //最后要取min
	}

    return 0;
}

周六

F. Clear the String(区间dp)

读完题就知道是区间dp

但是不知道怎么利用这个条件

答案是当s[i] == s[j]时可以减一次

这样可以包括所有的情况

我开始的时候只考虑了k和k+1

这样漏了相隔的情况

而s[i] == s[j]也包括相邻的情况

总是差一点点,多练

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 500 + 10;
int dp[N][N], a[N][N], n;
char s[N];

int main()
{
	scanf("%d%s", &n, s + 1);
	memset(dp, 0x3f, sizeof dp);
	_for(i, 1, n) dp[i][i] = 1;

	_for(len, 2, n)
		for(int i = 1; i + len - 1 <= n; i++)
		{
			int j = i + len - 1;
			_for(k, i, j - 1)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] - (s[i] == s[j]));
		}
	printf("%d\n", dp[1][n]);
 
    return 0;
}

C. The Hard Work of Paparazzi(dp+优化)

这个看起来很长,其实题意很简单

很容易想到一个n方的dp

关键是怎么优化

我想了一会儿,突然发现r非常小

比较异常的数据范围是突破口

r最大只有500,意味着只要时间差大于1000就一定可以达到

所以时间差比较大的部分可以直接取区间最值,用一个变量存一下就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 1e5 + 10;
int dp[N], x[N], y[N], t[N], n, r;

int main()
{
	scanf("%d%d", &r, &n);
    _for(i, 1, n) scanf("%d%d%d", &t[i], &x[i], &y[i]);
    x[0] = y[0] = 1;

    memset(dp, -127, sizeof dp);
    dp[0] = 0;
    int mx = 0, r = 0;

    _for(i, 1, n)
        for(int j = i - 1; j >= 0; j--)
        {
            if(abs(t[i] - t[j]) <= 1000)
            {
                if(abs(t[i] - t[j]) >= abs(x[i] - x[j]) + abs(y[i] - y[j]))
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            else
            {
                while(r < j) mx = max(mx, dp[++r]);
                dp[i] = max(dp[i], mx + 1);
                break;
            }
        }

    int ans = 0;
    _for(i, 1, n) ans = max(ans, dp[i]);
    printf("%d\n", ans);
 
    return 0;
}

C. Match Points(二分答案+尺取法)

开始直接尺取法,发现不对

因为r的起点不好判断

于是想到二分答案,左端点就前key个,r从key+1开始

这样尺取法就不会有问题了,保证不会选重复

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int a[N], n, z;

bool check(int key)
{
    int ans = 0, l = 1, r = key + 1;
    for(l = 1; l <= n; l++)
    {
        while(r <= n && a[r] - a[l] < z) r++;
        if(r > n) break;
        ans++; r++;
    }
    return ans >= key;
}

int main()
{
	scanf("%d%d", &n, &z);
    _for(i, 1, n) scanf("%d", &a[i]);
    sort(a + 1, a + n + 1);

    int l = 0, r = n / 2 + 1;
    while(l + 1 < r)
    {
        int m = l + r >> 1;
        if(check(m)) l = m;
        else r = m;
    }
    printf("%d\n", l);
 
    return 0;
}

C. Propagating tree(简化问题+dfs序+树状数组)

这题一开始理解错题意了……

没看样例解释……自作孽

首先简化问题,先考虑没有正负正负的情况

每次操作一个子树加一个值,询问一个点的值

其实很套路了,dfs序转化成区间,成了区间修改单点查询

差分+树状数组就可以实现

那么开始拓展

这道题有一个正负正负的关系,怎么处理呢?

可以发现,如果深度都为偶数或都为奇数,不用取负,否则取负

所以可以开两个树状数组,一个记录偶数深度的区间修改情况,一个记录奇数的

当前询问的是偶数深度,那就加上偶数的减去奇数的就可以了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int f[N][2], a[N], L[N], R[N], k[N], cnt, n, m;
vector<int> g[N];

int lowbit(int x) { return x & -x; }

void add(int x, int p, int id)
{
    for(; x <= n; x += lowbit(x))
        f[x][id] += p;
}

int sum(int x, int id)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        res += f[x][id];
    return res;
}

void dfs(int u, int fa, int d)
{
    L[u] = ++cnt;
    k[u] = d % 2;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u, d + 1);
    }
    R[u] = cnt;
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) scanf("%d", &a[i]);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1, 0, 1);
    while(m--)
    {
        int op, x, val;
        scanf("%d%d", &op, &x);
        if(op == 1)
        {
            scanf("%d", &val);
            add(R[x] + 1, -val, k[x]);
            add(L[x], val, k[x]);
        }
        else printf("%d\n", a[x] + sum(L[x], k[x]) - sum(L[x], k[x] ^ 1));
    }

    return 0;
}

周日

Cook Pancakes!

自己智障没有特判直接交,WA了一发

要特判一下n <= k的情况

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    if(n <= k) puts("2");
    else printf("%d\n", (2 * n + k - 1) / k);

    return 0;
}


Xor Transformation(异或)

x ^ T = y

T = x ^ y

所以直接异或x ^ y就行了

但是要使得x ^ y < x

我们看y的最高位 最高位前面的0是没有影响

如果x的对应位为1,那么x ^ y < x

否则x ^ y > x

如果对应位为0,就转化成前一种情况,异或上这一位使得其为1

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)

typedef long long ll;
ll x, y, ans1, ans2;

int main()
{
    scanf("%lld%lld", &x, &y);

    int maxy;
    for(int i = 60; i >= 0; i--)
        if(y & (1LL << i))
        {
            maxy = i;
            break;
        }

    if(x & (1LL << maxy))
    {
        puts("1");
        printf("%lld\n", x ^ y);
    }
    else
    {
        x ^= 1LL << maxy;
        puts("2");
        printf("%lld %lld\n", 1LL << maxy, x ^ y);
    }

    return 0;
}

Matrix Equation(高斯消元)

首先对应每一位相等可以列一个方程组

固定一列后,可以列含有n个未知数的n个方程

然后高斯消元求自由变元的数量就可以了

找了个模板直接抄

注意有无解的情况

这样一算是n的四次方的

用bitset优化

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 200 + 10;
const int mod = 998244353;
bitset<N> a[N];
int A[N][N], B[N][N], n;

int binpow(int a, int b)
{
    int res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = 1LL * res * a % mod;
        a = 1LL * a * a % mod;
    }
    return res;
}

int Guess()
{
    int r = 0, c = 0;
	while(r < n && c < n)
	{
		int m_r = r;
		_for(i, r + 1, n)
			if(a[i][c] > a[m_r][c]) m_r = i;
		if(m_r != r) swap(a[r], a[m_r]);
		if(!a[r][c])
		{
			a[r][c] = 0;
			c++;
			continue;
		}
		_for(i, r + 1, n)
			if(a[i][c])
				a[i] ^= a[r];
		r++; c++;
	}
	_for(i, r, n)
		if(a[i][n])
			return -1;
	return n - r;
}

int main()
{ 
    scanf("%d", &n);
    rep(i, 0, n)
        rep(j, 0, n)
            scanf("%d", &A[i][j]);
    rep(i, 0, n)
        rep(j, 0, n)
            scanf("%d", &B[i][j]);
    
    int ans = 0;
    rep(j, 0, n)
    {
        rep(i, 0, n) a[i].reset();
        rep(i, 0, n)
            rep(k, 0, n)
            {
                if(k != i) a[i][k] = A[i][k];
                else a[i][k] = abs(B[i][j] - A[i][i]);    
            }
        int t = Guess();
        if(t == -1)
        {
            puts("0");
            return 0;
        }
        ans += t;
    }
    printf("%d\n", binpow(2, ans));

    return 0;
}

Bit Sequence(数位dp)

算是写过的比较复杂的数位dp了

[0, L]符合某个条件的x的个数,八成数位dp(比赛时竟然没反应过来)

那数位dp该记录什么呢,注意到进位很不好处理

注意到m只有100,所以只要记录前7位,这样最多进位一次

因为2^7 = 128 > 100 开始时我弄成6位搞错了

那么如果判断符不符合条件呢?

只要后七位是什么数,剩余位有多少个1 以及7位之后有多少个连续的1(进位)

注意数位dp是从高位到低位的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 110;
int a[N], b[N], len, m;
ll dp[70][2][2][2][150], L;

int cal(int x)
{
    int res = 0;
    for(; x; x >>= 1)
        res += x & 1;
    return res;
}

bool check(int k, int con, int num)
{
    num--;
    _for(i, 1, m)
    {
        num++;
        if(num == 128)
        {
            num = 0;
            k -= con - 1;
            k = (k % 2 + 2) % 2;
        }
        if((k + cal(num)) % 2 != b[i]) return false;
    }
    return true;
}

ll dfs(int pos, int pre, int con, int k, int num, int limit)
{
    if(pos > len) return check(k, con, num);
    if(dp[pos][pre][con][k][num] != -1 && !limit) return dp[pos][pre][con][k][num];
    ll res = 0, mx = limit ? a[len - pos + 1] : 1;
    _for(i, 0, mx)
    {
        if(len - pos + 1 <= 7) res += dfs(pos + 1, i, con, k, num * 2 + i, i == mx && limit);
        else 
        {
            if(pre && i) res += dfs(pos + 1, i, (con + 1) % 2, (k + i) % 2, num, i == mx && limit);
            else res += dfs(pos + 1, i, i, (k + i) % 2, num, i == mx && limit);
        }
    }
    if(!limit) dp[pos][pre][con][k][num] = res;
    return res;
} 

ll solve(ll x)
{
    len = 0;
    while(x) a[++len] = x % 2, x /= 2;
    memset(dp, -1, sizeof dp);
    return dfs(1, 0, 0, 0, 0, 1);
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d%lld", &m, &L);
        _for(i, 1, m) scanf("%d", &b[i]);
        printf("%lld\n", solve(L));
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值