CSP-S 模拟 19/11/05

在这里插入图片描述
首先要么把御符打完,要么不打御符贪心打兵符
打兵符:贪心用最大的打掉最小的,双指针扫一遍
打御符:尽量用小的去打,双指针挪着走就可以了
打死所有的御符过后并不是用兵符打完,而是贪心打负数的兵符
然后同上述打兵符的情况,如果值大于 0 0 0 就直接打,否则打小于 0 的
有一个细节是要判一下能不能把御符打完,否则不能按 0 来打

这种恶心的写不出对拍的可能只能静态瞪眼法挑错了,不知道有没有更稳妥的方法

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
typedef long long ll;
cs int N = 1e6 + 5;
int n, m1, m2; ll ans;
struct node{
	int val, num;
	bool operator < (cs node &a) cs{ return val < a.val; }
}A[N], B[N], C[N], a[N], b[N], c[N];
ll Solve1(){ // 把御符打爆然后贪心打兵符 
	ll ans = 0;
	bool flg = 1;
	for(int i = 1, j = 1; i <= n; i++){
		while(j <= m1 && a[i].num && a[i].val >= b[j].val){
			if(a[i].num < b[j].num){
				b[j].num -= a[i].num; 
				a[i].num = 0;
				break;
			} a[i].num -= b[j].num; j++;
		} if(j > m1) { flg = 0; break; }
	} 
	reverse(a + 1, a + n + 1);
	for(int i = 1, j = 1; i <= n; i++){
		if(!a[i].num) continue;
		while(j <= m2 && a[i].num && a[i].val >= c[j].val && c[j].val < 0){
			int num = min(a[i].num, c[j].num);
			a[i].num -= num;
			c[j].num -= num;
			ans += 1ll * num * (a[i].val - c[j].val);
			if(c[j].num == 0) ++j; 
		} if(!flg) if(a[i].num && a[i].val > 0) ans += 1ll * a[i].num * a[i].val;
	} 
	return ans;
}
ll Solve2(){ // 只打兵符御符碰都不碰  
	// 己方兵符应该从大的开始打 
	ll ans = 0;
	reverse(a + 1, a + n + 1); 
	for(int i = 1, j = 1; i <= n; i++){
		while(j <= m2 && a[i].num && a[i].val >= c[j].val){
			int num = min(a[i].num, c[j].num);
			a[i].num -= num;
			c[j].num -= num;
			ans += 1ll * num * (a[i].val - c[j].val);
			if(c[j].num == 0) ++j; 
		} if(j > m2) break;
		if(a[i].val < c[j].val) break;
	} return ans;
} 
int main(){
	n = read(), m1 = read(), m2 = read();
	for(int i = 1; i <= n; i++) A[i].val = read(), A[i].num = read();
	for(int i = 1; i <= m1; i++) B[i].val = read(), B[i].num = read();
	for(int i = 1; i <= m2; i++) C[i].val = read(), C[i].num = read();
	sort(A + 1, A + n + 1);
	sort(B + 1, B + m1 + 1);
	sort(C + 1, C + m2 + 1);
	memcpy(a, A, sizeof(A));
	memcpy(b, B, sizeof(B));
	memcpy(c, C, sizeof(C));
	ans = Solve1(); 
	memcpy(a, A, sizeof(A));
	memcpy(b, B, sizeof(B));
	memcpy(c, C, sizeof(C));
	ll sum = Solve2();
	ans = max(ans, sum);
	cout << ans; return 0;
}

在这里插入图片描述
简要题意:
一个图,给每一个点分配一个颜色红或者黑
求出至少含一条红边和一条蓝边的最小生成树使得权值和为 X X X
问方案数?
先求出一棵最小生成树强制为红,分类讨论
如果权值和 s u m > x sum>x sum>x ,那么无解
如果权值和 s u m < x sum<x sum<x
接着考虑每一条非树边,想要知道它能不能为黑
强制选它的最小生成树就是加上它再在树形成的环上断一条最大的边
如果正好就是 X X X,那么我们把这个边打一个标记
如果比 X X X 大,那么它是黑是红没有关系
如果比 X X X 小,那么必须强制为红
考虑打标记的边,至少选一条边出来染成黑色,方案数就是 2 c n t − 1 2^{cnt}-1 2cnt1
然后每个没有关系的边对答案有 2 的贡献
如果 s u m = x sum=x sum=x
同上求出 c n t cnt cnt,那么对于原树的 n − 1 n-1 n1 条边和这 c n t cnt cnt 条边,不合法当且仅当它们全部同色
全部的减去不合法的,方案数为 2 m − 2 ∗ 2 m − ( n − 1 ) − c n t 2^m-2*2^{m-(n-1)-cnt} 2m22m(n1)cnt
然后倍增求一下 m a x max max 就可以了

考场上写了 s u m > x sum>x sum>x 的情况就走人了,然后华丽暴零
反思
1. 过于信任大样例,没有写暴力,没有对拍
2. 思路不严密,问题想了一半就走人了

以后考试一定要拍,然后可以把思路写到草稿纸上完善好了再开始打
做题和人生都是这样
不要妄自菲薄也不要盲目自信
要看到希望却又不忘记安危

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
typedef long long ll;
cs int N = 2e5 + 5;

cs int Mod = 1e9 + 7;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
int ksm(int a, int b){ int ans = 1; for(;b;b>>=1, a=mul(a,a)) if(b&1) ans = mul(ans, a); return ans;  }

int T, n, m; ll X, sum;
struct edge{ int u, v, w; }e[N];
bool cmp(edge a, edge b){ return a.w < b.w; }
bool vis[N];

int first[N], nxt[N], to[N], w[N], tot;
void adde(int x, int y, int z){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z; }
int mx[N][20], dep[N], fa[N][20];
int mo[N]; int find(int x){ return x == mo[x] ? x : mo[x] = find(mo[x]);}

void dfs(int u, int f){
	for(int i = 1; i <= 18; i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
		mx[u][i] = max(mx[u][i-1], mx[fa[u][i-1]][i-1]);
	}
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(t == f) continue;
		fa[t][0] = u; dep[t] = dep[u] + 1;
		mx[t][0] = w[i]; dfs(t, u); 
	}
}
void Kruskal(){
	sort(e + 1, e + m + 1, cmp);
	for(int i = 1; i <= n; i++) mo[i] = i;
	sum = 0;
	for(int i = 1, ct = 0; ct < n-1 && i <= m; i++){
		int x = find(e[i].u), y = find(e[i].v);
		if(x^y){ 
			mo[x] = y; vis[i] = true;
			adde(e[i].u, e[i].v, e[i].w); 
			adde(e[i].v, e[i].u, e[i].w);
			sum += (ll)e[i].w; ++ct; 
		}
	} dep[1] = 1; dfs(1, 0);	
}
int ct, ans;
int Get(int u, int v){
	if(dep[u] < dep[v]) swap(u, v);
	int Mx = 0;
	for(int i = 18; ~i; i--){
		if(dep[fa[u][i]] >= dep[v]){
			Mx = max(Mx, mx[u][i]);
			u = fa[u][i];
		}
	} if(u == v) return Mx;
	for(int i = 18; ~i; i--){
		if(fa[u][i] ^ fa[v][i]){
			Mx = max(Mx, mx[u][i]);
			Mx = max(Mx, mx[v][i]);
			u = fa[u][i];
			v = fa[v][i];
		}
	} 
	Mx = max(Mx, mx[u][0]);
	Mx = max(Mx, mx[v][0]);
	return Mx;
}
void ck(int u, int v, int w){
	int mx = Get(u, v);
	if(sum + w - mx < X) return;
	if(sum + w - mx == X){ ++ct; return; } 
	ans = add(ans, ans);
}
void Solve(){
	n = read(), m = read(); scanf("%lld", &X);
	for(int i = 1; i <= m; i++){
		e[i].u = read(); e[i].v = read(); e[i].w = read();
	} Kruskal(); ct = 0; ans = 2;
	if(sum > X){ puts("0"); return; }
	if(sum == X){
		for(int i = 1; i <= m; i++){
			if(vis[i]) continue;
			ck(e[i].u, e[i].v, e[i].w);
		} cout << add(ksm(2, m), mul(2, Mod - ksm(2, m - ct - (n - 1)))) << '\n';
		return;
	}
	for(int i = 1; i <= m; i++){
		if(vis[i]) continue;
		ck(e[i].u, e[i].v, e[i].w);
	} ans = mul(ans, add(ksm(2, ct), Mod - 1));
	cout << ans << '\n';
}
void Clear(){
	memset(vis, 0, sizeof(vis));
	memset(first, 0, sizeof(first)); tot = 0;
}
int main(){
	T = read();
	while(T--) Solve(), Clear(); 
	return 0;
}

在这里插入图片描述
题意:给一个带问号的字符串,问包涵给定串的第 k k k 大的串
两个串的长度, n ≤ 5 e 4 n\le 5e4 n5e4 m ≤ 20 m\le 20 m20 q ≤ 1 e 5 q\le 1e5 q1e5 次询问
考场上面想了一个分层图的做法,就是一层拆成 m m m 个点表示匹配到第几位,把填的数抽象为边权,然后就转换为在 d a g dag dag 上求字典序 k k k 大的路径,每次找一个方案数最多的重儿子,向它连边,显然最后是一棵树,每次跳轻边方案数至少除 2,加上倍增,每次的复杂度就是 O ( l o g ( n m ) l o g ( k ) ) O(log(nm)log(k)) O(log(nm)log(k))
也可以直接 d p dp dp
d p i , j dp_{i,j} dpi,j 表示 i i i 以前已经匹配了 j j j 位,后面的合法方案数
枚举下一位填什么
d p i , j = ∑ k = 0 9 d p i + 1 , n x t [ j ] [ k ] dp_{i,j}=\sum_{k=0}^9dp_{i+1,nxt[j][k]} dpi,j=k=09dpi+1,nxt[j][k]
k m p kmp kmp 预处理即可
然后一样对 d p i , j dp_{i,j} dpi,j 求出它的重儿子 d p i + 1 , k dp_{i+1,k} dpi+1,k
有一个比较讨厌的是还要处理向后 2 i 2^i 2i 步走成的 10 进制数
复杂度 O ( q l o g ( n m ) l o g ( k ) ) O(qlog(nm)log(k)) O(qlog(nm)log(k))

法2:
考虑到如果只剩下最后十几个问号的时候方案数已经有 1 e 18 1e18 1e18 了,我们可以对询问离线
一起往下走,只有有询问在下面的点的区间的时候我们才向下走
由于最开始一直是偏向 0 那边走,最后的分支很少,于是就可过了
复杂度不太会证,但是对询问离线一起处理的思想还是比较巧妙

#include<bits/stdc++.h>
#define cs const
using namespace std;
typedef long long ll;
ll read(){
	ll cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 5e4 + 5, M = 25, L = 17;
cs ll inf = 1e18 + 1;
int T, n, q;
char s[25]; int m;
char S[N];
int nxt[25], pw[25]; 
ll dp[N][M], rkl[L + 1][N][M], rkr[L + 1][N][M], val[L + 1][N][M];
int trans[M][M], fa[L + 1][N][M], qw[N];
void Clear(){
	memset(dp, 0, sizeof(dp));
	memset(trans, 0, sizeof(trans));
	memset(nxt, 0, sizeof(nxt));
}
cs int Mod = 1e9 + 7;
int mul(int a, int b){ return 1ll * a * b % Mod; }
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
ll Add(ll a, ll b){ return a + b >= inf ? inf : a + b; }
void kmp(){
	for(int i = 2, j = 0; i <= m; i++){
		while(j && s[j + 1] != s[i]) j = nxt[j];
		if(s[j + 1] == s[i]) ++ j; nxt[i] = j;
	}
	for(int i = 0; i < m; i++){
		for(int j = 0; j <= 9; j++){
			int nx = i;
			while(nx && s[nx + 1] - '0' != j) nx = nxt[nx];
			if(s[nx + 1] - '0' == j) ++nx;
			trans[i][j] = nx;
		}
	}
	for(int i = 0; i <= 9; i++) trans[m][i] = m;
}
int kth(ll k){
	if(k > dp[1][0]) return -1;
	int x = 1, y = 0, res = 0;
	while(x <= n){
		for(int i = L; ~i; i--){
			if(x + (1 << i) - 1 <= n && rkl[i][x][y] < k && k <= rkr[i][x][y]){
				res = add(mul(res, pw[i]), val[i][x][y]);
				k -= rkl[i][x][y]; y = fa[i][x][y], x += (1 << i);
			}
		} if(x > n) break;
		for(int i = 0; i <= 9; i++){
			if(k > dp[x + 1][trans[y][i]]) k -= dp[x + 1][trans[y][i]];
			else{
				res = add(mul(res, 10), i);
				++x; y = trans[y][i];
				break;
			}
		}
	} return res;
}

void Solve(){
	n = read(), q = read();
	scanf("%s", s + 1); m = strlen(s + 1);
	kmp();
	scanf("%s", S + 1); 
	for(int i = 0; i <= m; i++) dp[n + 1][i] = (i == m);
	for(int i = n; i; i--){
		for(int j = 0; j <= m; j++){
			ll mx = -1;
			int nxt = -1;
			for(int k = 0; k <= 9; k++){
				if(S[i] == '?' || S[i] - '0' == k){
					dp[i][j] = Add(dp[i][j], dp[i + 1][trans[j][k]]);
					if(dp[i + 1][trans[j][k]] > mx) nxt = k, mx = dp[i + 1][trans[j][k]];
				}
			} 
			fa[0][i][j] = trans[j][nxt];
			val[0][i][j] = nxt; rkl[0][i][j] = 0;
			for(int k = 0; k < nxt; k++){
				if(S[i] == '?' || S[i] - '0' == k){
					rkl[0][i][j] = Add(rkl[0][i][j], dp[i + 1][trans[j][k]]);
				}
			} rkr[0][i][j] = Add(rkl[0][i][j], dp[i + 1][trans[j][nxt]]);
		}
	}
	for(int k = 1; k < L; k++){
		int len = 1 << k - 1;
		for(int i = 1; i + (1 << k) - 1 <= n; i++){
			for(int j = 0; j <= m; j++){
				int x = fa[k - 1][i][j];
				fa[k][i][j] = fa[k - 1][i + len][x];
				val[k][i][j] = add(mul(val[k - 1][i][j], pw[k - 1]), val[k - 1][i + len][x]);
				rkl[k][i][j] = Add(rkl[k - 1][i][j], rkl[k - 1][i + len][x]);
				rkr[k][i][j] = Add(rkl[k - 1][i][j], rkr[k - 1][i + len][x]);
			}
		}
	}
	while(q--){
		ll k = read();
		cout << kth(k) << '\n';
	}
}
int main(){
	freopen("1.in","r",stdin);
	T = read();
	pw[0] = 10; for(int i = 1; i <= L; i++) pw[i] = mul(pw[i - 1], pw[i - 1]);
	while(T--) Solve(), Clear();
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值