NOI.ac2020省选模拟赛12

比赛链接

A.union

problem

给出两棵分别为\(n,m\)个节点的树A和树B。有Q次操作,每次将A树中\((a,b)\)路径上的每个点和B树中\((c,d)\)路径上的每对点\((i,j)\)两两之间的友好度\(f(i,j)\)加上\(c\)。最后输出所有的\(i\times j \times f(i,j)\)的异或和。

\(1\le n,m \le10^4,Q\le 5\times 10^5\)

solution

对两棵树分别树链剖分,然后每次询问都会在两棵树中找到最多\(log\)级别条链。然后枚举每一对链,因为每条链上点的编号都是连续的,所以对于每一对链中的每个点对友好度+c的操作可以看做是将二维平面内一个矩形内的值+c。二维差分一下。最后求答案的时候再二维前缀和一下就行了。

code

/*
* @Author: wxyww
* @Date:   2020-06-12 08:09:32
* @Last Modified time: 2020-06-12 08:37:43
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
typedef long long ll;
const int N = 20010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
#define pi pair<int,int>
vector<pi>a[2];
int ans[10003][10003];
struct SSS {
	struct node {
		int v,nxt;
	}e[N << 1];
	int head[N],ejs,tot;
	void add(int u,int v) {
		e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;
	}
	int dep[N],fa[N],dfn[N],siz[N],son[N],top[N];
	
	SSS() {
		ejs = tot = 0;
		memset(fa,0,sizeof(fa));
		memset(dfn,0,sizeof(dfn));
		memset(siz,0,sizeof(siz));
		memset(son,0,sizeof(son));
		memset(top,0,sizeof(top));
		memset(head,0,sizeof(head));
	}

	void dfs1(int u,int father) {
		dep[u] = dep[father] + 1;
		fa[u] = father;
		siz[u] = 1;
		for(int i = head[u];i;i = e[i].nxt) {
			int v = e[i].v;
			if(v == father) continue;
			dfs1(v,u);
			siz[u] += siz[v];
			if(siz[v] > siz[son[u]]) son[u] = v;
		}
	}
	void dfs2(int u,int father,int ttop) {
		top[u] = ttop;
		dfn[u] = ++tot;
		if(!son[u]) return;
		dfs2(son[u],u,ttop);
		for(int i = head[u];i;i = e[i].nxt) {
			int v = e[i].v;
			if(v == father || v == son[u]) continue;
			dfs2(v,u,v);
		}
	}
	int get(int x,int y,int opt) {
		while(top[x] != top[y]) {
			if(dep[top[x]] > dep[top[y]]) {
				a[opt].push_back(make_pair(dfn[top[x]],dfn[x]));
				x = fa[top[x]];
			}
			else {
				a[opt].push_back(make_pair(dfn[top[y]],dfn[y]));
				y = fa[top[y]];
			}
		}
		if(dep[x] < dep[y]) swap(x,y);
		a[opt].push_back(make_pair(dfn[y],dfn[x]));
	}
}T1,T2;

int main() {
	// freopen("1.in","r",stdin);
	// printf("%d %d\n",T1.ejs,T1.tot);
	int n = read();
	for(int i = 1;i < n;++i) {
		int u = read(),v = read();
		T1.add(u,v);T1.add(v,u);
	}
	T1.dfs1(1,0);T1.dfs2(1,0,1);

	int m = read();
	for(int i = 1;i < m;++i)  {
		int u = read(),v = read();
		T2.add(u,v);T2.add(v,u);
	}
	T2.dfs1(1,0);T2.dfs2(1,0,1);


	int Q = read();
	while(Q--) {
		a[0].clear();a[1].clear();
		int a1 = read(),a2 = read(),b1 = read(),b2 = read(),c = read();

		T1.get(a1,a2,0);T2.get(b1,b2,1);

		for(vector<pi>::iterator it = a[0].begin();it != a[0].end();++it) {
			int x1 = (*it).first,x2 = (*it).second;
			for(vector<pi>::iterator is = a[1].begin();is != a[1].end();++is) {
				int y1 = (*is).first,y2 = (*is).second;
				ans[x1][y1] += c;
				ans[x2 + 1][y1] -= c;
				ans[x1][y2 + 1] -= c;
				ans[x2 + 1][y2 + 1] += c;
			}
		}
	}

	for(int i = 1;i <= n;++i)
		for(int j = 1;j <= m;++j) {
			ans[i][j] += ans[i][j - 1] + ans[i - 1][j] - ans[i - 1][j - 1];
		}
	

	ll anss = 0;

	for(int i = 1;i <= n;++i) {
		for(int j = 1;j <= m;++j) {
			anss ^= 1ll * i * j * ans[T1.dfn[i]][T2.dfn[j]];
		}
	}
	cout<<anss;

	return 0;
}

B.ZYB的画图计划

problem

给一幅长度为n的画涂色。要求最终第\(i\)个位置的颜色是\(a_i\)。每种颜色只能涂一次而且只能涂一个区间,如果之前某个位置有颜色,涂完之后会覆盖之前的颜色。所有格子被涂次数之和最多可以是多少。

solution

可以发现对于1,2,3,2,1,4,4这种情况,2,3,2肯定要在1涂完之后再涂,并且肯定要在\([2,4]\)这个区间内涂。所以我们把这个序列看成是\([1,5]\)\([6,7]\)两个区间进行决策。然后再对这两个区间进行递归操作。

然后问题就转化成了对于一些区间决策先后顺序,使得涂得点数之和最大。设\(f[l][r]\)表示涂了第\(l\)个区间到第\(r\)个区间最多能涂的格子数。转移就是\(f[l][r]=\max\{f[l][k-1]+f[k+1][r]\}+w[i][j]\)。这样转移的复杂度是\(O(n)\)的,不能承受。发现最优的决策点只在\(k=l\)\(k=r\)两个位置。这样转移的复杂度就是\(O(1)\)的了。

code

/*
* @Author: wxyww
* @Date:   2020-06-12 11:52:45
* @Last Modified time: 2020-06-14 07:38:36
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 100010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}

#define pi pair<int,int>
pi b[N];
int a[N],lst[N];
int n,m;
ll f[5010][5010];
int cnt;
ll DP() {
	for(int i = 1;i <= cnt;++i) {
		for(int j = 1;j <= cnt;++j)
			f[i][j] = 0;
	}
	for(int len = 1;len <= cnt;++len) {
		for(int l = 1;l + len - 1 <= cnt;++l) {
			int r = l + len - 1;
			f[l][r] = max(f[l + 1][r],f[l][r - 1]) + (b[r].second - b[l].first + 1);
		}
	}
	return f[1][cnt];
}
ll solve(int l,int r,int col) {
	if(l > r) return 0;
	cnt = 0;
	int p = l;
	vector<pi>tmp;
	tmp.clear();
	ll ret = 0;
	while(p <= r) {
		if(a[p] == col) {++p;ret += DP();cnt = 0;continue;}
		b[++cnt] = make_pair(p,lst[a[p]]);
		tmp.push_back(make_pair(p,lst[a[p]]));
		p = lst[a[p]] + 1;
	}
	ret += DP();
	for(vector<pi>::iterator it = tmp.begin();it != tmp.end();++it) {
		ret += solve((*it).first,(*it).second,a[(*it).first]);
	}
	return ret;
}
int main() {
	n = read(),m = read();
	for(int i = 1;i <= n;++i) {
		a[i] = read();lst[a[i]] = i;
	}
	cout<<solve(1,n,0);
	return 0;
}
/*
5 3
1 2 3 2 1
5 3
1 1 2 2 3


6 4
1 2 1 4 3 4
*/

C.T2

problem

读入一个字符串\(S\),问有多少对子串\(u,v\),满足\(u,v\)都是回文串且位置相交。

\(|S|\le 2\times 10^6\)

solution

转化为求有多少对回文子串不相交,对于一个回文中心\(p\),如果以他为中心的回文串最长是\(f[p]\),那么对于所有右端点小于\(p\)的回文串,贡献+1,。对于所有右端点小于\(p-1\)的回文串贡献+2,...,对于所有右端点小于\(p-\frac{f[p]}{2}\)的点,贡献+\(\frac{f[p]}{2}\)

发现需要的操作就是区间加一个等差数列,区间求和。用一个线段树就好了。因为可以离线,所以差分一下跑两遍后缀和可以实现等差数列的复原,然后正着求一边前缀和就可以完成区间查询。

code

/*
* @Author: wxyww
* @Date:   2020-06-12 08:50:50
* @Last Modified time: 2020-06-12 10:47:29
*/

//有多少回文子串不相交
//线段树区间加等差数列,区间和
//sb了,离线直接用一个数组处理即可
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
#define int ll
const int N = 2000010,mod = 998244353,inv2 = (mod + 1) / 2;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
void upd(int &x,int y) {
	x += y;
	x >= mod ? x -= mod : 0;
}
int a[N];
char s[N],S[N << 1];
int f[N << 1];
int n,m;
void manacher() {
	int id = 0,mx = 0;
	for(int i = 1;i <= m;++i) {
		if(id + mx >= i) f[i] = min(f[id + id - i],id + mx - i);
		while(i - f[i] - 1 >= 1 && i + f[i] + 1 <= m && S[i - f[i] - 1] == S[i + f[i] + 1]) ++f[i];
		if(i + f[i] > id + mx) id = i,mx = f[i]; 
	}
}
signed main() {
	// freopen("1.in","r",stdin);

	scanf("%s",s + 1);
	n = strlen(s + 1);
	S[++m] = '#';
	for(int i = 1;i <= n;++i) {
		S[++m] = s[i];
		S[++m] = '#';
	}

	manacher();
	int ans = 0;
	ll t = 0;

	for(int i = 2;i < m;++i) {
		int p = i / 2,len = f[i] / 2 - (i & 1);
	
		t += (f[i] + 1) / 2;
		t %= mod;
		if(p - len - 1 <= p - 1) {	
			a[p - 1]++;
			if(p - len - 2 >= 0) a[p - len - 2]--;
		}
	}

	for(int i = n;i >= 1;--i) upd(a[i],a[i + 1]);
	for(int i = n;i >= 1;--i) upd(a[i],a[i + 1]);

	for(int i = 1;i <= n;++i) upd(a[i],a[i - 1]);

	for(int i = 2;i < m;i += 2) {
		int p = i / 2,len = f[i] / 2;
		upd(ans,(a[p + len] - a[p - 1]) % mod);
	}

	for(int i = 3;i < m;i += 2) {
		int p = i / 2,len = f[i] / 2;
		upd(ans,(a[p + len] - a[p]) % mod);
	}

	cout<<((t * (t - 1) % mod * inv2 % mod - ans) % mod + mod) % mod<<endl;

	return 0;
}
posted @ 2020-06-14 10:16  wxyww  阅读(6)  评论(0编辑  收藏  举报