2018 国庆雅礼 NOIP 培训

\(Day3\)

\(U\)

传送门

矩阵的范围比较小只有\(5000\),但是操作次数比较多有\(3e5\),最后只需要查询一次。

操作次数多查询少,我们首先就会想到差分。

那么由于操作的图形不是传统的正方形而是三角形,所以需要在原本的二维差分上进行膜改。

考虑二维差分的意义,因为最后每个点的值是他左上方的所有数的前缀和,那么在差分数组上某个位置修改,就相当于对那个位置到矩阵右下角的一个正方形修改。

这样子我们就可以发现三角形就是将那条对角线上的每个点的查分数组加上\(s\),相邻的一条对角线减去\(s\),三角形左下角减去\(s\),三角形右下角加上\(s\)

那么在对每条对角线维护一个差分数组,最后先将差分数组搞出来,再将查分数组前缀和得到答案数组。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define int long long

const int N = 5000;

int b[N + 50][N + 50], n, djx[N + 50][N + 50], a[N + 50][N + 50], q;

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}

signed main()
{
	Read(n); Read(q);
	for (int i = 1, r, c, l, s; i <= q; i++)
	{
		Read(r); Read(c); Read(l); Read(s);
		if (r + l <= n) b[r + l][c] -= s; 
		if (r + l <= n && c + l <= n) b[r + l][c + l] += s;
		djx[c - r + 1050][c] += s; if (c + l <= n) djx[c - r + 1050][c + l] -= s;
		djx[c + 1 - r + 1050][c + 1] -= s; if (c + l + 1 <= n) djx[c + 1 - r + 1050][c + l + 1] += s;
	}
	for (int i = 1; i <= n; i++)	
		for (int j = 1; j <= n; j++)
			a[i][j] += a[i - 1][j - 1] + djx[j - i + 1050][j];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			b[i][j] += a[i][j];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			b[i][j] += b[i][j - 1];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)	
			b[i][j] += b[i - 1][j];
	int ans = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			ans ^= b[i][j];
	printf("%lld", ans);
	return 0;
}

\(V\)

传送门

\(n\)很小,所以可以考虑状压。

发现状压的状态数就是本质不同子序列个数,对于这个问题有经典\(dp\)

\[dp_i = dp_{pre_{i_0} + dp_{pre_{i_1}}} \]

那么这样最大就是\(fib\)数列,所以跑的过去。

但是直接记录状态会出现哈希冲突导致答案错误,这是因为有可能最高位上有若干个\(0\),这些\(0\)不会被记录在状态里。

普遍的解决办法是把\(n\)\(k\)也压进状态里但是会\(Tle\)

一个小\(Trick\)是可以强制最高位的更高一位或上\(1\),这一位永远也不会用到,正好解决了哈希冲突,还是很妙的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>

using namespace std;

double dp[(1 << 24) + 50];

int p[(1 << 24) + 50];

char st[50];

map<int, double> dpp;

double ans;

void Print(int s, int n)
{
	for (int i = 1; i <= n; i++)
		if ((s >> i) & 1) cout << 'W'; else cout << 'B';
	puts("");
	return;
}

int Zh(int s, int i)
{
	return (((s >> (i + 1)) << i) | (s & ((1 << i) - 1)));
}

double Dfs(int n, int k, int s)
{
	if (!k) return 0;
	if (s <= 16777216)
	{
		if (p[s]) return dp[s];
	}
	else if (dpp[s]) return dpp[s];
	double tmp = 0;
	for (int i = 0; i * 2 < n; i++)
	{
		int p1 = ((s >> i) & 1), p2 = ((s >> (n - i - 1)) & 1), l = n - i - 1;
		double tmp1 = Dfs(n - 1, k - 1, Zh(s, i)), tmp2 = Dfs(n - 1, k - 1, Zh(s, l));
		if (p1) tmp1 += 1.0; if (p2) tmp2 += 1.0;
		tmp1 = max(tmp1, tmp2);
		if (i != n - 1 - i) tmp = tmp + tmp1 * 2.0; else tmp = tmp + tmp1;
	}
	tmp /= n;
	if (s <= 16777216) return p[s] = 1, dp[s] = tmp;
	else return dpp[s] = tmp;
}

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	scanf("%s", st + 1);
	int s = 0;
	for (int i = 1; i <= n; i++) s = s | ((1 << (i - 1)) * (st[i] == 'W'));
	s |= (1 << n);
	printf("%.10lf\n", Dfs(n, k, s));
	return 0;
}

\(W\)

传送门

首先选取的翻转的边的交集肯定是空集,那么对于一条边就有三种状态:必须选,必须不选,可以选。

再考虑将所有要选的边的集合拿出来,答案就是其中度数为奇数的点的个数除以二。

这是因为某一条链在结尾和开头处的贡献是\(1\),在中间节点的贡献是\(2\)

而如果一个点同时是两条链的开头或结尾,那么这两条链可以连接起来会更优。

那么\(dp_{x_{0, 1}}\)表示\(x\)\(x\)父亲相连的那一条边选还是不选下奇数度数点的个数,翻转总长度。

考虑最终每个点至多只能向上传递一条链,其它链都在这个点以下的子树中被处理。

那么\(tmp1\)\(tmp2\)分别记录和\(x\)相连的深度比它大的那些边中有奇数条还是偶数条需要翻转,如果是奇数条那么有一条需要传递到\(x\)的父亲,如果是偶数条可以互相配对无需传递。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define fi first
#define se second

const int N = 100000;
const int INF = 999999999;

int head[N + 50], num, n;

struct Node
{
	int next, to, dis;
} edge[N * 2 + 50];

struct Element
{
	int cnt, sum;
	bool operator < (const Element &tmp) const
	{
		return cnt == tmp.cnt ? sum < tmp.sum : cnt < tmp.cnt;
	}
	Element operator + (const Element &tmp) const
	{
		return (Element){cnt + tmp.cnt, sum + tmp.sum};
	}
} dp[N + 50][2]; 

void Addedge(int u, int v, int w)
{
	edge[++num] = (Node){head[u], v, w};
	head[u] = num;
	return;
}

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}

void Dfs(int x, int fa, int typ)
{
	Element tmp1 = (Element){0, 0}, tmp2 = (Element){INF, INF};
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa) continue;
		Dfs(v, x, edge[i].dis);
		Element temp1, temp2;
		temp1 = tmp1; temp2 = tmp2;
		tmp1 = min(temp1 + dp[v][0], temp2 + dp[v][1]);
		tmp2 = min(temp1 + dp[v][1], temp2 + dp[v][0]);
	}
	if (typ == 2 || typ == 0)
	{
		dp[x][0] = min(tmp1, tmp2 + (Element){1, 0});
	}
	else  dp[x][0] = (Element){INF, INF};
	if (typ == 2 || typ == 1)
	{
		dp[x][1] = min(tmp1 + (Element){1, 1}, tmp2 + (Element){0, 1});
	}
	else dp[x][1] = (Element){INF, INF};
	//cout << dp[x][1].cnt << " " << dp[x][1].sum << " " << dp[x][0].cnt << " " << dp[x][0].sum << endl;
	return;
}

int main()
{
	Read(n);
	for (int i = 1, a, b, c, d; i <= n - 1; i++)
	{
		Read(a); Read(b); Read(c); Read(d);
		if (d == 2) Addedge(a, b, 2), Addedge(b, a, 2);
		else Addedge(a, b, c ^ d), Addedge(b, a, c ^ d);
	}
	Dfs(1, 0, 0);
	printf("%d %d\n", dp[1][0].cnt >> 1, dp[1][0].sum);
	return 0;
}
posted @ 2020-09-15 09:58  Tian-Xing  阅读(182)  评论(0编辑  收藏  举报