noip2020题解

T1

注意要先除后乘
懒得写高精
code:

#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
struct edge {
	int v, nxt;
} e[N << 1];
int p[N], eid;
int init() {
	memset(p, -1, sizeof p);
	eid = 0;
}
void insert(int u, int v) {
	e[eid].v = v;
	e[eid].nxt = p[u];
	p[u] = eid ++;
}
ll gcd(ll x, ll y) {
//	printf("%lld %lld\n", x, y);
	return x? gcd(y % x, x) : y;
}
ll a[N], b[N];
int n, m, in[N], ha[N];
queue<int> q;
vector<int> g[N];
void bfs() {
	for(int i = 1; i <= n; i ++) a[i] = 0, b[i] = 1;
	for(int i = 1; i <= n; i ++) 
		if(!in[i]) q.push(i), a[i] = 1;
	while(q.size()) {
		int u = q.front(); q.pop();
		int k = g[u].size();
		for(int i = 0; i < k; i ++) {
			int v = g[u][i];
			ll aa = a[u], bb = b[u];
			ll d = gcd(k, aa);
			bb *= (k / d);
			aa /= d;
			d = gcd(aa, bb);
			if(d) aa /= d, bb /= d;
			
			ll cc = a[v], dd = b[v];
			d = gcd(bb, dd);
			b[v] = bb / d * dd;
			
			a[v] =  cc * (b[v] / dd) + (b[v] / bb) * aa;
			
			
			d = gcd(a[v], b[v]);
			if(d) a[v] /= d, b[v] /= d;
			
			in[v] --;
			if(!in[v]) q.push(v);
		}
	}
}
int main() {
//	printf("%lld\n", gcd(4, 6));
// 	freopen("water.in","r",stdin);
// 	freopen("water.out","w",stdout);
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) {
		int k;
		scanf("%d", &k);
		for(int j = 1; j <= k; j ++) {
			int v;
			scanf("%d", &v);
			in[v] ++;
			g[i].push_back(v);
		}
		if(!k) ha[i] = 1;
	}
	bfs();
	for(int i = 1; i <= n; i ++) if(ha[i]) {
		printf("%lld %lld\n", a[i], b[i]);
	}
	return 0;
}

T2

考虑 z z z函数
可以发现,对于一个前缀 S 1... i S_{1...i} S1...i,对应的 z [ i + 1 ] z[i+1] z[i+1]表示的是 S i + 1.... n S_{i+1....n} Si+1....n的最长公共前缀
那么这个长度 z [ i + 1 ] / i + 1 z[i+1]/i +1 z[i+1]/i+1就是 S 1... i S_{1...i} S1...i重复的次数
发现不管复刻几次, F ( C ) F(C) F(C)的值只有两个,一个是去掉第一个前缀的,一个是整个串的
然后直接做的行了
code:

#include<bits/stdc++.h>
#define mod1 1000000007
#define mod2 1000000009
#define ll long long
#define N 1300005
using namespace std;
int t, n, gs[35];
char st[N];
int mi1[N], mi2[N], m1[N], m2[N], a[N], g[N], z[N];
ll f[N][28];
void get() {
	for(int i = 0; i <= n; i ++) z[i] = 0;
	z[1] = n; int len = 0, k = 0;
	for(int i = 2; i <= n; i ++) {
		if(i <= len) z[i] = min(z[i - k + 1], len - i + 1);
		while(i + z[i] < n && a[i + z[i]] == a[z[i] + 1]) ++ z[i];
		if(i + z[i] - 1 > len) len = i + z[i] - 1, k = i;
	}
}
int main() {
// 	freopen("string.in","r",stdin);
// 	freopen("string.out","w",stdout);
	scanf("%d", &t);
	while(t --) {
		scanf("%s", st + 1);
		n = strlen(st + 1);
		
		for(int i = 1; i <= n; i ++) a[i] = (st[i] - 'a' + 1);
		
		get(); 
	//	for(int i = 1; i <= n; i ++) printf("%d ", z[i]); printf("\n");
		
		for(int i = 1; i <= 26; i ++) gs[i] = 0;	
		for(int i = n; i >= 1; i --) {
			g[i] = g[i + 1];
			gs[a[i]] ^= 1;
			if(gs[a[i]]) g[i] ++;
			else g[i] --;
		}
		
		for(int i = 1; i <= 26; i ++) gs[i] = 0;
		int gss = 0;
		for(int i = 1; i <= n; i ++) {
			gs[a[i]] ^= 1;
			if(gs[a[i]]) gss ++;
			else gss --;
			f[i][gss] ++;
		}				
		for(int i = 1; i <= n; i ++) {
			for(int j = 1; j <= 26; j ++)
				f[i][j] += f[i][j - 1];
			for(int j = 0; j <= 26; j ++)
				f[i][j] += f[i - 1][j];	
		}
			
		ll ans = 0;
		for(int i = 1; i <= n - 1; i ++) {
			/*for(int i = len; i < n; i += len) {
				int hou = g[i + 1];
				ans += f[len - 1][hou];
			}*/
			int ha = min(z[i + 1], n - i - 1) / i + 1;
			int s1 = (ha + 1) / 2, s2 = ha / 2;
	//		printf("%d   %d %d\n", i, s1, s2);
			int hou = g[i + 1];
			ans += 1ll * f[i - 1][hou] * s1;
			int zg = g[1];
			ans += 1ll * f[i - 1][zg] * s2;
		//	printf("%lld\n", ans);
		}
		printf("%lld\n", ans);
		
		for(int i = 0; i <= n; i ++)
			for(int j = 0; j <= 26; j ++) 
				f[i][j] = 0;
		for(int i = 0; i <= n + 1; i ++) g[i] = m1[i] = m2[i] = mi1[i] = mi2[i] = 0;
	}
	return 0;
}

T3

首先考虑只有两种颜色怎么搞
先考虑一个操作,把当前栈的白色全部移到上面:
假设有三个栈, x , y , z , z 是 空 的 , 另 外 两 个 是 满 的 x,y,z,z是空的,另外两个是满的 x,y,zz
y y y进行操作,假设 y y y a a a个白色
分几步来做
操作1:

  • 1、把 x x x的上面 a a a个移到 z z z
  • 2、对于 y y y的每个颜色,如果是白色就移到 x x x里,否则移到 z z z里,把 y y y移空
  • 3、把 z z z中的 m − a m-a ma个移动回 y y y
  • 4、把 x x x中的 a a a个白色移动到 y y y
  • 5、把 z z z中剩下的 a a a个移动回 x x x
    可以发现 z z z仍然是空的, x x x中的顺序也没有变,只有 y y y的白色被提到了上面
    在这里插入图片描述
    这个操作的代码很简单:
    code:
// em 表示empty,就是空栈,钦定为最后一个就好了
// a 是栈,s表示x栈中有s个白色
void workk(int x, int y, int s) {
	for(int i = 1; i <= s; i ++) mv(y, em);
	for(int i = 1; i <= m; i ++) {
		int co = col[a[x][gs[x]]];
		if(!co) mv(x, y);
		else mv(x, em);
	}
	for(int i = 1; i <= m - s; i ++) mv(em, x);
	for(int i = 1; i <= s; i ++) mv(y, x);
	for(int i = 1; i <= s; i ++) mv(em, y);
}

非常简单
然后再考虑怎么把一个颜色全部移到同一个栈里
操作2:

  • 1、对两个栈都做上面那个操作1,使得白色都在栈的最上面
  • 2、把这两个栈上的白色全部移到空栈
  • 3、然后再把其中一个栈的全部移到另一个栈
  • 4、把全白的栈上的全部移到目前的空栈(当然我这么写是为了方便,其实可以重新钦定一个空栈,因为我的空栈固定是n+1,所以要多做一步)
    code:
int work(int x, int y) {
	int s1 = count(x, 0), s2 = count(y, 0); //数x栈,y栈中白色的个数
	workk(x, y, s1), workk(y, x, s2);//
	for(int i = 1; i <= s1; i ++) mv(x, em);
	for(int i = 1; i <= min(s2, m - s1); i ++) mv(y, em);
	for(int i = 1; i <= min(s2, m - s1); i ++) mv(x, y);
	for(int i = 1; i <= min(m, s1 + s2); i ++) mv(em, x);
	if(s1 + s2 >= m) return 0;//这个有什么用待会讲
	return 1;	
}

这样就可以把白色的移到同一个栈里,同理另一个栈就是黑色的
然后考虑这题怎么做

考虑分治,把颜色 < = n / 2 <=n/2 <=n/2的染成白色,把另一边染成黑色,然后把白色的全部移到一边,黑色的全部移到一遍,然后分治下去
具体怎么做呢?
可以发现一个显然的性质,如果两个栈的白色加起来不到 m m m,那么黑色的加起来一定大于 m m m
拿两个指针指着前一半的栈和后一半的栈,假设是 x , y x,y x,y
首先还是用操作2把白色的全部移到 x x x
如果白色不够m怎么办?
这里就是上面代码没有讲的地方,白色不够说明黑色一定够,那么把白色全部移到 x x x之后 y y y一定全都是黑色的
所以移完 y + + y++ y++即可,反过来同理 x + + x++ x++

其实就是相当于一个归并排序的操作,具体可以看代码:
code:

#include<bits/stdc++.h>
#define N 405
using namespace std;
int em, gs[N], a[N][N], ans[820000 * 3][2], anss, col[N], id[N], n, m;
int count(int x, int o) {
	int ret = 0;
	for(int i = 1; i <= gs[x]; i ++) ret += col[a[x][i]] == o;
	return ret;
}
void mv(int x, int y) { //printf("%d %d\n", x, y);
	a[y][++ gs[y]] = a[x][gs[x]];
	gs[x] --;
	++ anss; ans[anss][0] = x, ans[anss][1] = y;
}
void workk(int x, int y, int s) {
	for(int i = 1; i <= s; i ++) mv(y, em);
	for(int i = 1; i <= m; i ++) {
		int co = col[a[x][gs[x]]];
		if(!co) mv(x, y);
		else mv(x, em);
	}
	for(int i = 1; i <= m - s; i ++) mv(em, x);
	for(int i = 1; i <= s; i ++) mv(y, x);
	for(int i = 1; i <= s; i ++) mv(em, y);
}
int work(int x, int y) { //printf("       %d %d\n", x, y);
	int s1 = count(x, 0), s2 = count(y, 0);
	workk(x, y, s1), workk(y, x, s2);
	for(int i = 1; i <= s1; i ++) mv(x, em);
	for(int i = 1; i <= min(s2, m - s1); i ++) mv(y, em);
	for(int i = 1; i <= min(s2, m - s1); i ++) mv(x, y);
	for(int i = 1; i <= min(m, s1 + s2); i ++) mv(em, x);
	if(s1 + s2 >= m) return 0;
	return 1;	
}
void solve(int *id, int n) {
	if(n == 1) return;
	int mid = n / 2;
	for(int i = 1; i <= mid; i ++) col[id[i]] = 0;
	for(int i = mid + 1; i <= n; i ++) col[id[i]] = 1;
	int x = 1, y = mid + 1;
	while(x <= mid && y <= n) {
		while(count(id[x], 0) == m && x <= mid) x ++;
		while(count(id[y], 1) == m && y <= n) y ++;
		if(x > mid || y > n) break;
		if(work(id[x], id[y])) y ++;
		else x ++;
	}
	solve(id, mid), solve(id + mid, n - mid);
}
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) 
		for(int j = 1; j <= m; j ++) 
			scanf("%d", &a[i][j]), gs[i] = m;
	for(int i = 1; i <= n; i ++) id[i] = i;
	em = n + 1;
	solve(id, n);
	printf("%d\n", anss);
	for(int i = 1; i <= anss; i ++)
		printf("%d %d\n", ans[i][0], ans[i][1]);
	return 0;
}

是不是很短

posted @ 2021-05-20 13:56  lahlah  阅读(45)  评论(0编辑  收藏  举报