2022NOIP A层联测30

A. 分配

都推成和根的关系,然后取 \(lcm\), 用什么东西维护一下质因子的幂次

好像大家都是从上往下推,但是我从下往上推,用\(map\)维护了一下,进行了启发式合并

复杂度好像是 \(nlog^2\)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 200005;
const int mod = 998244353;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n;

int prime[maxn], cnt, mi[maxn];
bool flag[maxn];
int inv[maxn];
void init(){
	for(int i = 1; i <= 200000; ++i)inv[i] = qpow(i, mod - 2);
	for(int i = 2; i <= 200000; ++i){
		if(!flag[i])prime[++cnt] = i, mi[i] = i;
		for(int j = 1; j <= cnt && prime[j] * i <= 200000; ++j){
			flag[i * prime[j]] = 1; mi[i * prime[j]] = prime[j];
			if(i % prime[j] == 0)break;
		}
	}
}
int head[maxn], tot;
struct edge{int to, net, a, b;}e[maxn << 1 | 1];
void ckmx(int &x, int y){if(x < y)x = y;}
void add(int u, int v, int a, int b){
	e[++tot] = {v, head[u], a, b};
	head[u] = tot;
}
map<int, int> mp[maxn];
void merge(map<int, int> &x, map<int, int> &y){
	if(x.size() < y.size())swap(x, y);
	for(auto v : y)ckmx(x[v.first], v.second);
	y.clear();
}
void del(map<int, int> &x, int y){
	while(y > 1){
		if(x[mi[y]] > 0){
			--x[mi[y]];
			if(x[mi[y]] == 0)x.erase(x[mi[y]]);
		}
		y /= mi[y];
	}
}
void add(map<int, int> &x, int y){
	while(y > 1){
		++x[mi[y]];
		y /= mi[y];
	}

}
void dfs(int x, int fa){
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v != fa){
			dfs(v, x);
			del(mp[v], e[i].b);
			add(mp[v], e[i].a);
			merge(mp[x], mp[v]);
		}
	}
}
int val[maxn];
void sol(int x, int fa){
	mp[x].clear();
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v != fa){
			val[v] = 1ll * val[x] * e[i].b % mod * inv[e[i].a] % mod;
			sol(v, x);
		}
	}
}
void sol(){
	n = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read(), a = read(), b = read();
		int g = __gcd(a, b); a /= g; b /= g;
		add(u, v, a, b); add(v, u, b, a);
	}
	dfs(1, 0); 
	for(int i = 1; i <= n; ++i)val[i] = 1;
	for(auto v : mp[1]){
		val[1] = 1ll * val[1] * qpow(v.first, v.second) % mod;
	}
	sol(1, 0);
	int ans = 0;
	for(int i = 1; i <= n; ++i)ans = (ans + val[i]) % mod;
	printf("%d\n",ans);
	for(int i = 1; i <= n; ++i)head[i] = 0;
	tot = 0;
}

int main(){
	freopen("arrange.in","r",stdin);
	freopen("arrange.out","w",stdout);
	init();
	int t = read();
	for(int i = 1; i <= t; ++i)sol();
	return 0;
}

B. 串串超人

比较套路,但是我想了半年

线段树维护叶节点到 \(r\) 的信息

每次移动 \(r\), 当前连续 \(1\) 直接计算贡献,前面的在线段树上查询

因为后缀\(max\) 所以有单调性,每次线段树二分找一段区间覆盖成当前值

扫完这个连续段再回去把每个后缀的贡献单点修到线段树里

不过仔细思考一下发现这东西能用类似单调盏的方法优化到线性

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 500005;
char c[maxn];
int pre[maxn];
int n;
struct seg{
	struct node{
		ll sum;
		int len, tag, mi;
	}t[maxn << 2 | 1];
	void upd(int x, int val){
		t[x].mi = val;
		t[x].tag = val;
		t[x].sum = 1ll * t[x].len * val;
	}
	void push_down(int x){upd(x << 1, t[x].tag); upd(x << 1 | 1, t[x].tag); t[x].tag = 0;}
	void push_up(int x){t[x].mi = min(t[x << 1].mi, t[x << 1 | 1].mi); t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum;}
	void built(int x, int l, int r){
		t[x].len = r - l + 1;
		if(l == r)return;
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
	}
	void modify(int x, int l, int r, int L, int R, int val){
		if(L <= l && r <= R){upd(x, val); return;}
		if(t[x].tag)push_down(x);
		int mid = (l + r) >> 1;
		if(L <= mid) modify(x << 1, l, mid, L, R, val);
		if(R > mid) modify(x << 1 | 1, mid + 1, r, L, R, val);
		push_up(x);
	}
	int bound(int x, int l, int r, int val){
		if(l == r)return l + (t[x].mi >= val);
		if(t[x].tag)push_down(x);
		int mid = (l + r) >> 1;
		if(t[x << 1].mi < val)return bound(x << 1, l, mid, val);
		return bound(x << 1 | 1, mid + 1, r, val);
	}
	ll query(int x, int l, int r, int L, int R){
		if(L <= l && r <= R)return t[x].sum;
		if(t[x].tag)push_down(x);
		int mid = (l + r) >> 1; ll ans = 0;
		if(L <= mid)ans += query(x << 1, l, mid, L, R);
		if(R > mid)ans += query(x << 1 | 1, mid + 1, r, L, R);
		return ans;
	}
}t;
ll calc(ll x){return 1ll * (1 + x) * x / 2;}
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	n = read(); 
	scanf("%s",c + 1);
	for(int i = 1, las = 0; i <= n; ++i){
		if(c[i] == '0')las = i;
		pre[i] = las;
	}
	t.built(1, 1, n);
	ll ans = 0;
	for(int r = 1; r <= n; ++r){
		if(pre[r] == r)ans += t.query(1, 1, n, 1, r);
		else{
			ans += calc(r - pre[r]);
			int pos = t.bound(1, 1, n, r - pre[r]);
			if(pos <= pre[r])t.modify(1, 1, n, pos, pre[r], r - pre[r]);
			if(pre[r])ans += t.query(1, 1, n, 1, pre[r]);
			if(pre[r + 1] == r + 1){
				for(int j = pre[r] + 1; j <= r; ++j)t.modify(1, 1, n, j, j, r - j + 1);
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C. 多米诺游戏

考虑一个数,两次框他的必然不同,把框看成边,那么大概跟图有关

猜测是找环?

打开样例,把框画出来,发现可以拉成 \(2 \times n\) 的长条, 而每条边加两次保证了点的度数为偶数,那么直接欧拉回路即可

不过需要保证同一个框代表的边经过的时刻奇偶性不同,但是因为加边方式,好像我直接跑就是对的

是否有人能证明这样直接跑的正确性?

严谨的解法大概可以参考 \(APJ\) 巨佬,(如果他写日总结)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 600005;
int n;
int head[maxn], tot = 1;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
} 
int deg[maxn];
int d[maxn][2];
bool check(){
	for(int i = 1; i <= n; ++i){
		if(deg[d[i][0]] == deg[d[i][1]] && deg[d[i][0]] == 2)return false;
	}
	return true;
}
bool del[maxn << 1 | 1];
vector<int>h[maxn];
int cnt;
void dfs(int x, int id){
	for(int &i = head[x]; i; i = e[i].net)if(!del[i]){
		int v = e[i].to;  --deg[x]; --deg[v]; del[i] = del[i ^ 1] = true;
		dfs(v, id);
	}
	h[id].push_back(x);
}
void sol(){
	printf("%d %d\n",2, n);
	for(int i = 1; i <= n + n; ++i){
		if(deg[i])dfs(i, ++cnt);
	}
	for(int i = 1; i <= cnt; ++i)h[i].pop_back();
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		for(int j = 0; j < s; ++j)printf("%d ",h[i][j]);
	}
	printf("\n");
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		for(int j = h[i].size() - 1; j >= s; --j)printf("%d ",h[i][j]);
	}
	printf("\n");
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		for(int j = 2; j <= s; j += 2)printf("LR");
		if(s & 1)printf("U");
	}
	printf("\n");
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		for(int j = 2; j <= s; j += 2)printf("LR");
		if(s & 1)printf("D");
	}
	printf("\n");
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		printf("U");
		for(int j = 3; j <= s; j += 2)printf("LR");
		if((s & 1) == 0)printf("U");
	}
	printf("\n");
	for(int i = 1; i <= cnt; ++i){
		int s = h[i].size(); s >>= 1;
		printf("D");
		for(int j = 3; j <= s; j += 2)printf("LR");
		if((s & 1) == 0)printf("D");
	}
	printf("\n");
}
int main(){
	//freopen("domino.in","r",stdin);
	//freopen("domino.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i){
		int u = read(), v = read();
		d[i][0] = u; d[i][1] = v;
		add(u, v); add(v, u); add(u, v); add(v, u);
		++deg[u]; ++deg[u]; ++deg[v]; ++deg[v];
	}
	if(check())sol();
	else printf("-1\n");
	return 0;
}

D. 大师

又是一个没见过并且难以想到的 \(trick\)

考虑把奇数位都异或 \(1\), 我们的操作就成了交换相邻两个不同数

考虑一个位置 \(x\) 被经过的次数,是 \(|\sum_{i = 1}^{x} a_i - \sum_{i = 1}^{x} b_i|\)

于是设 \(f_{i, j}\) 表示前 \(i\) 个位置, \(\sum a - \sum b = j\) 的方案数, \(g_{i, j}\)表示操作数,然后直接转移即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 2005;
const int base = 2003;
const int mod = 1e9 + 7;
int n;
char s[maxn], t[maxn];
int a[maxn], b[maxn];
int f[maxn][maxn + maxn], g[maxn][maxn + maxn];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
void sol(){
	n = read();
	scanf("%s%s",s + 1, t + 1);
	for(int i = 1; i <= n; ++i)a[i] = s[i] == '?' ? -1 : s[i] - '0';
	for(int i = 1; i <= n; ++i)b[i] = t[i] == '?' ? -1 : t[i] - '0';
	for(int i = 1; i <= n; i += 2){
		if(a[i] != -1)a[i] ^= 1;
		if(b[i] != -1)b[i] ^= 1;
	}
	f[0][base] = 1;
	for(int i = 0; i < n; ++i)
		for(int j = -i; j <= i; ++j)if(f[i][j + base]){
			int la = a[i + 1] == -1 ? 0 : a[i + 1], ra = a[i + 1] == -1 ? 1 : a[i + 1];
			int lb = b[i + 1] == -1 ? 0 : b[i + 1], rb = b[i + 1] == -1 ? 1 : b[i + 1];
			for(int p = la; p <= ra; ++p)
				for(int q = lb; q <= rb; ++q){
					add(g[i + 1][j + base + p - q], (g[i][j + base] + 1ll * abs(j + p - q) * f[i][j + base]) % mod);
					add(f[i + 1][j + base + p - q], f[i][j + base]);
				}
		}
	printf("%d\n",g[n][base]);
	for(int i = 0; i <= n; ++i)
		for(int j = -i - 3; j <= i + 3; ++j)f[i][j + base] = g[i][j + base] = 0;
}
int main(){
	freopen("master.in","r",stdin);
	freopen("master.out","w",stdout);
	int t = read(); for(int i = 1; i <= t; ++i)sol();
	return 0;
}

posted @ 2022-11-18 16:03  Chen_jr  阅读(48)  评论(2编辑  收藏  举报