qbxt D4 && NOIP提高组考前刷题冲刺班(第四场)

A

模拟即可,用struct和map优化



#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e4 + 7;
int n;
char s[MAXN];
string t;
map<string, int> mp;
int tot;
struct rec {
	string r;
	ll v;
	int kd;
} a[MAXN];

inline void output(int x) {
	if(a[x].kd == 1) {
		printf("%lld\n", a[x].v);
	} else cout << a[x].r << '\n';
	return ;
}

inline int getid(string s) {
	if(mp.find(s) != mp.end())return mp[s];
	mp[s] = ++tot;
	return mp[s];
}

int main() {
	// freopen("test.in", "r", stdin);
	// freopen("test.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf("%s", s);
		t.clear();
		int m = strlen(s);
		int kd = 0;
		for(int i = 0; i < m; ++i) {
			if(s[i] == '+') {
				kd = i;
				break;
			}
			if(s[i] == '=') {
				kd = -i;
				break;
			}
		}
		if(kd == 0) {
			for(int i = 0; i < m; ++i)t.push_back(s[i]);
			if(mp.find(t) != mp.end()) {
				int id = mp[t];
				output(id);
			} else puts("no");
		} else if(kd < 0) {
			kd = -kd;
			for(int i = 0; i < kd; ++i)t.push_back(s[i]);
			bool qwq = 0;
			for(int i = kd + 1; i < m; ++i)if(s[i] == '"') {
					qwq = 1;
					break;
				}
			//是字符串类型,我们自闭
			if(qwq) {
				string  tmp;
				tmp.clear();
				for(int i = kd + 1; i < m; ++i) {
					if(isalpha(s[i]) || isdigit(s[i])) {
						tmp.push_back(s[i]);
					}
				}
				int id = getid(t);
				a[id].r = tmp;
				a[id].kd = 2;
			} else {
				ll tmp = 0;
				for(int i = kd + 1; i < m; ++i) {
					if(isdigit(s[i])) {
						tmp = tmp * 10 + s[i] - '0';
					}
				}
				int id = getid(t);
				a[id].v = tmp;
				a[id].kd = 1;
			}
		} else {
			for(int i = 0; i < kd; ++i)t.push_back(s[i]);
			bool qwq = 0;
			++kd;
			for(int i = kd + 1; i < m; ++i)if(s[i] == '"') {
					qwq = 1;
					break;
				}
			if(mp.find(t) != mp.end()) {
				int id = mp[t];
				if(qwq) {//字符串加!
					if(a[id].kd == 1)continue;
					string  tmp;
					tmp.clear();
					for(int i = kd + 1; i < m; ++i) {
						if(isalpha(s[i]) || isdigit(s[i])) {
							tmp.push_back(s[i]);
						}
					}
					a[id].r += tmp;
				} else {//数字加!
					if(a[id].kd == 1) {
						ll tmp = 0;
						for(int i = kd + 1; i < m; ++i) {
							if(isdigit(s[i])) {
								tmp = tmp * 10 + s[i] - '0';
							}
						}
						a[id].v += tmp;
					} else {
						string  tmp;
						tmp.clear();
						for(int i = kd + 1; i < m; ++i) {
							if(isdigit(s[i])) {
								tmp.push_back(s[i]);
							}
						}
						a[id].r += tmp;
					}
				}
			}
		}
	}
	return 0;
}

B

最难的一题(doge)

首先对于一个字符串有用的只有一个:每个字符数量

那么我们首先可以枚举Alice和Bob选择的字符串是哪两个

然后我们想知道Alice选择的字符串能否用一种字典序打爆所有其他字符串

首先,我们确定的字符集第一个字符,一定要满足数量大于等于所有的其他串!

然后你会发现,这样满足的字符其实有很多个

先放a再放b,和先放b再放a是一样的

因为我们都是先把a比第i个字符串小的剃掉,再把b比第i个字符串小的剃掉

那么我们会发现这个本质是相同的

也就是说,无论这些字符以什么顺序去填本质都是相同的!

先枚举一个i从1~26,表示我们字典集第i位

然后再去枚举一个k去尝试填他

把所有剩下的还没有被删掉的暴力出现小于k的删除

即可

如果长度不同,还要特判每个字符是否是当且字符串最后一个

等等被唐爷爷卡了

aab
aabb,先a后b,先b后a本质不同.....

然后么得了/..真被卡了......

code:



#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 7;
//虽然我觉得跑不过去
//但是我比较牛逼
int n;
char s[MAXN];

struct rec {
	int b[30];
} a[MAXN];

int ed;
//考虑一个字符串能成为老大哥
//当且仅当他按照自己最优势(最多的)的排完序后
//比所有其他字符串的字典序都小
//也就是说暴力比较即可
//但是还有一种情况
//就是说如果我有的你没有,还是老子赢
//因为我可以把它放到开头然后直接胜利
//另外就是他字符集大于等于老子
//这个很好看,就是我的字符比到最后一步也一定要比他少
//不行萎了萎了
//喵喵喵
int que[MAXN], tmp[MAXN], vis[MAXN];
inline int chk(int x) {
	int tl = 0;
	for(int i = 1; i <= n; ++i)if(i != x)que[++tl] = i;
	for(int i = 0; i < 26; ++i)vis[i] = 0;
	for(int k = 0; k < 26; ++k) {//排名第一???
		int rc = -1;
		int mcnt = 0;
		for(int j = 0; j < 26; ++j) {//n^2*26^2,随便过
			if(!vis[j] && a[x].b[j]) {//我要有才行...
				bool flg = 1;
				int cnt = 0;
				for(int i = 1; i <= tl; ++i) {
					int u = que[i];
					if(a[u].b[j] < a[x].b[j]) {
						cnt++;
					}
					if(a[u].b[j] > a[x].b[j]) {
						flg = 0;
						break;
					}
				}
				if(flg) {
					if(cnt > mcnt) {
						mcnt = cnt;
						rc = j;
					}
				}
			}
		}
		if(rc < 0)break;
		vis[rc] = 1;
		int hd = 0;
		for(int i = 1; i <= tl; ++i) {
			int u = que[i];
			if(a[u].b[rc] == a[x].b[rc]) {//相同!!
				tmp[++hd] = u;
			}
		}
		tl = hd;
		if(tl == 0)return 1;
		for(int i = 1; i <= tl; ++i)que[i] = tmp[i];
	}
	return 0;
}

int main() {
	freopen("T2.in", "r", stdin);
	freopen("T2.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf("%s", s);
		int m = strlen(s);
		for(int k = 0; k < m; ++k)a[i].b[s[k] - 'a']++;
	}
	int ans = 0;
	for(int i = 1; i <= n; ++i) {
		ans += chk(i); //qieke
	}
	printf("%d\n", ans);
	return 0;
}

C

首先我们要找出所有的区间?然后计算每个位置的贡献?

那要算出有多少个区间包括某个点

可以发现,我们能够尺取这个区间

因为随着区间变长,逆序对数单调不减

就是说[l,r]>=k[l,r]>=k,对于每一个r

那么我们找到最大的那个l,[1~l,r]这些区间都满足条件

可以用一个二度差分实现一个前缀区间都满足的加

然后在右端点+1处再打一个-L的标记

这样我们求一个二次前缀和和一个一次前缀和,就能知道一个位置被匹配多少次

然后就能算每个位置的贡献,从而得到答案!

还有一个版,问有多少区间相交

唐爷爷:点减边容斥

就是每个点被覆盖的贡献-每条边被覆盖的贡献

当然可以做补集转换,就是说考虑有多少不相交的区间

那可以枚举一个最大的左端点,然后看小于这个左端点的右端点有多少个即可

code:

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) (x&(-x))

using namespace std;
const int MAXN = 4e6 + 7;
const int P = 1e9 + 7;
const int inv2 = (P + 1) / 2;
int n, a[MAXN];

namespace fastIO {
#define BUF_SIZE (1<<20)
	static char buf[BUF_SIZE], *p1 = buf, *pend = buf;
	inline char nc() {
		if(p1 == pend) {
			p1 = buf;
			pend = buf + fread(buf, 1, BUF_SIZE, stdin);
		}
		return *p1++;
	}
	inline int read() {
		int x = 0;
		char s = nc();
		for(; !isdigit(s); s = nc());
		for(; isdigit(s); s = nc())x = (x << 3) + (x << 1) + s - '0';
		return x;
	}
}
using namespace fastIO;

struct BIT {
	int tr[MAXN];
	inline void add(int x, int V) {
		for(; x <= n; x += lowbit(x))tr[x] += V;
	}
	inline int qry(int x) {
		ll ret = 0;
		for(; x; x -= lowbit(x))ret += tr[x];
		return ret;
	}
} tr;

ll Ni, cf[MAXN], cf2[MAXN], K;

inline void add3(int L, int R, int x) {
	ll ret = R - L - tr.qry(x);//有多少比他小的,就是比他大的
	Ni += ret;
	tr.add(x, 1);
	return;
}

inline void pop(int x) {
	tr.add(x, -1);
	int ret = tr.qry(x - 1);
	Ni -= ret;
	return ;
}

inline void add2(int x) {
	Ni += tr.qry(x - 1);
	tr.add(x, 1);
	return ;
}

int main() {
	// freopen("test.in", "r", stdin);
	n = read();
	K = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	int L = 1;
	for(int R = 1; R <= n; ++R) {
		add3(L, R, a[R]);
		while(L <= R && Ni >= K) {
			pop(a[L]);
			if(Ni < K) {
				add2(a[L]);
				break;//说明这个是极限了
			}
			++L;
		}
		//相当于所有左端点到这的区间都qwq了
		if(Ni >= K) {
			cf2[1]++;
			cf2[L + 1]--;
			cf[R + 1] -= L; //左端点在这取的所有终结
		}
	}
	for(int i = 1; i <= n; ++i) {
		cf2[i] += cf2[i - 1];
	}
	for(int i = 1; i <= n; ++i) {
		cf[i] += cf[i - 1];
	}
	for(int i = 1; i <= n; ++i) {
		cf2[i] += cf2[i - 1];
	}
	for(int i = 1; i <= n; ++i)cf[i] += cf2[i], cf[i] %= P;
	//前缀和!!!!!!!!!
	ll ans = 0;
	for(int i = 1; i <= n; ++i) {
		ans = (ans + (1ll * cf[i] * (cf[i] - 1) % P * inv2 % P)) % P;
	}
	printf("%lld\n", ans);
	return 0;

}
/*
6
abbcc
aabbc
abbbc
aaabc
aabcc
abccc
*/


D

计数题...

std:

可以发现,如果只有一个根的时候我们可以拓扑序树上dp一下

然后我们再考虑有两个根,会发现他们之间的路径拿出来,其中删除点的方案一定是这个路径的一个区间

那么我们就能设计fl,rf_{l,r}表示这个路径的[l,r][l,r]都被删除的方案数

然后转移就是考虑l+1和r-1

方法就是这一段的siz和和那一个子树的siz求个组合数,然后乘上他内部自由分配的方案数

my:

暴力出奇迹

考虑一个树的拓扑序为p!isizi\frac{p!}{\prod_isiz_i}

然后我们只需要考虑这个链上第一个被删掉的是哪个,后面都是本质不同的了

会发现我们断掉一条边可以把树分成两个连通块,然后就有了这两个点先被删掉方案

再每个相邻的做一下即可

代码如下


#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5 + 7;
const int P = 1e9 + 7;
int home[MAXN], nxt[MAXN], to[MAXN], ccnt, n, p1, p2;
int vis[MAXN];

inline ll ksm(ll x, ll y) {
	ll ans = 1;
	while(y) {
		if(y & 1)ans = ans * x % P;
		x = x * x % P;
		y >>= 1;
	}
	return ans;
}

inline void ct(int x, int y) {
	ccnt++;
	nxt[ccnt] = home[x];
	home[x] = ccnt;
	to[ccnt] = y;
}

ll fac[MAXN], ifac[MAXN], ans1, inv[MAXN], ans;

inline void init() {
	fac[0] = 1;
	ifac[0] = 1;
	for(int i = 1; i <= n; ++i) {
		fac[i] = fac[i - 1] * i % P;
	}
	ifac[n] = ksm(fac[n], P - 2);
	for(int i = n - 1; i >= 1; --i)ifac[i] = ifac[i + 1] * (i + 1) % P;
	for(int i = 1; i <= n; ++i)inv[i] = ifac[i] * fac[i - 1] % P;
	return ;
}

//尺取,一次跳两个点!!
//随便选取一个点开始即可
//O(n) chk 应该不会qaq吧??

int fa[MAXN], q[MAXN], tot;
bool flg = 1;

inline void dfs1(int u, int F) {
	if(u == p2) {
		int x = u;
		while(x != p1) {
			q[++tot] = x;
			x = fa[x];
		}
		q[++tot] = x;
		flg = 0;
		return ;
	}
	if(!flg)return ;
	for(int i = home[u]; i; i = nxt[i]) {
		int v = to[i];
		if(v == F)continue;
		fa[v] = u;
		dfs1(v, u);
	}
	return ;
}

int siz[MAXN];

inline void dfs2(int u, int F) {
	siz[u] = 1;
	for(int i = home[u]; i; i = nxt[i]) {
		if(vis[i])continue;
		int v = to[i];
		if(v == F)continue;
		dfs2(v, u);
		siz[u] += siz[v];
	}
	ans1 = ans1 * inv[siz[u]] % P;
}

inline ll C(int x, int y) {
	return fac[x] * ifac[y] % P * ifac[x - y] % P;
}

inline void solve() {
	dfs1(p1, 0);
	//关键是我们要找个边...QAQ
	//好吧...其实可以神秘法找边
	int rc = 0;
	for(int i = 1; i <= tot; i += 2) {
		int u = q[i];
		int x = q[i + 1];//你没了!
		for(int i = home[u]; i; i = nxt[i]) {
			int v = to[i];
			if(v == x) {
				rc = i;
				vis[rc] = vis[rc ^ 1] = 1;
				break;
			}
		}
		ans1 = 1;
		if(x != 0)dfs2(p1, 0);
		dfs2(p2, 0);//不访问标记
		if(x != 0)ans1 = ans1 * fac[siz[p1]] % P * fac[siz[p2]] % P;
		else ans1 = ans1 * fac[siz[p2]] % P;
		ans = (ans + ans1 * C(n, siz[p2]) % P) % P;
		vis[rc] = vis[rc ^ 1] = 0;
	}
	printf("%lld\n", ans);
}

inline void solve2() {
	ans1 = 1;
	dfs2(p1, 0);
	ans1 = ans1 * fac[siz[p1]] % P;
	printf("%lld\n", ans1);
	return ;
}

int main() {
	// freopen("test4.in", "r", stdin);
	// freopen("test4.out", "w", stdout);
	scanf("%d%d%d", &n, &p1, &p2);
	init();//预处理阶乘
	ccnt = 1;
	for(int i = 2, x, y; i <= n; ++i) {
		scanf("%d%d", &x, &y);
		ct(x, y);
		ct(y, x);
	}
	if(p1 != p2) {
		solve();
	} else {
		solve2();
	}
	return 0;
}

口胡T1

一个nmn*m矩阵,问是否存在子矩阵和在[k,2k]之间

都是正数

大于k的数都是没意义的,一个能让游戏直接结束,一个能全不选

所以说我们只需要枚举剩下点的最大值

假如这个最大值大于2k

因为我们扣去一行,如果剩下的第一次小于k,那么另一侧一定满足

考虑悬线,找到这个最大矩阵

口胡T2_pre

图上随机游走

高斯消元

口胡T2_niubi

一个01序列,问把这个序列改成全0/全1的期望代价

操作方式是一个指针只在开始位置,然后随机跳到位置j,然后代价为ji|j-i|

2^s然后高消显然是不行的

根据期望的线性性,我们只需要计算每一个i>ji->j经过期望次数

我们压压状态,会发现只和指针所在位置以及其他位置0/1态有关

fi,j,kf_{i,j,k}表示位置i是否等于位置j,当前指针是否在i,以及其他n-2个位置有多少个和i相同

转移分指针在不在i,从i走到哪里转移

高斯消元即可

然后我们最后枚举两个位置,利用这个期望次数拼起来即可

时间复杂度相当低.....n可以做150??