The 2024 ICPC Asia EC Regionals Online Contest (II)

写在前面

补题地址:https://codeforces.com/gym/105358

以下按个人向难度排序。

妈的 7 题秒完剩下的题感觉没一个能做的。

F 签到

wenqizhi 大神秒了,我看都没看。

Code by wenqizhi:

#include <bits/stdc++.h>
#define ll long long
const int N = 1e5 + 5;
ll a[N], n;

int main() {
	scanf("%d", &n);
	ll ans = 1500;
	for(int i = 1; i <= n; ++i)
	{
		scanf(" %d", &a[i]);
		ans += a[i];
		if(ans >= 4000)
		{
			printf("%d\n", i);
			return 0;
		}
	}
	printf("-1\n");	
	return 0;
}

A 枚举

显然每个队一定会选学校限制报名队伍数量最少的站,若该站的上限为 \(c\),则仅需考虑每个学校前 \(c\) 强的队伍即可。

然后将这些队伍排序,再枚举所有队伍检查他们单独插入后的排名即可,可使用 set 简单维护。

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define pr pair
#define mp make_pair

int read()
{
	int x = 0; bool f = false; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
	while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return f ? -x : x;
}

const int kInf = 1e9 + 10;
const int kN = 1e5 + 10;

int num, n, k;
int w[kN], schoolid[kN];
vector<pr <int, int> > team[kN];
map <string, int> id;
set <pr <int, int> > endteam;
int ans[kN]; 

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> k;
	
	int minc = kInf;
	for (int i = 1; i <= k; ++ i) {
		int c; cin >> c;
		minc = min(minc, c);
	}	
	for (int i = 1; i <= n; ++i) {
		string school; int x;
		cin >> x >> school;
		if (!id.count(school)) id[school] = ++ num; 
		team[id[school]].push_back(mp(-x, i));
		
		w[i] = x;
		schoolid[i] = id[school];
	}
	for (int i = 1; i <= num; ++ i) {
		sort(team[i].begin(), team[i].end());
		while (team[i].size() > minc) team[i].pop_back();
		for (auto x: team[i]) endteam.insert(x);
	}
	
	int cnt = 0;
	endteam.insert(mp(-1, n + 1));
	for (auto x: endteam) {
//		cout << x.first << " " << x.second << "\n";
		ans[x.second] = ++ cnt;
	}
	for (int i = 1; i <= n; ++ i) {
		if (ans[i]) continue;
		auto it = endteam.lower_bound(mp(-w[i], 0));
		ans[i] = ans[it->second] - 1;
	}
	for (int i = 1; i <= n; ++ i) cout << ans[i] << "\n";
	return 0;
}

J 贪心

保证了 \(c\le \frac{v}{\sum w}\),则实际上不需要考虑 \(v\) 的值,仅需最大化 \(\sum_i c_i\times W\) 即可。

发现相邻两个位置交换后仅影响这两个位置的 \(c_i\times W\),对前后缀每个位置的贡献无影响,一眼国王游戏,考虑微扰法发现仅需按照 \(\frac{c}{w}\) 升序排序即可。

code by wenqizhi:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
	ll x = 0; bool f = false; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
	while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return f ? -x : x;
}

const int N = 1e5 + 5;
int n;
struct node
{
	ll w, v, c;
	node(){ w = v = c = 0; }
	bool friend operator < (const node &a, const node &b)
	{ return a.c * b.w < b.c * a.w ;  }
}A[N];

int main() {
//	ios::sync_with_stdio(0);
//	cin.tie(0), cout.tie(0);
	n = read();
	ll V = 0, W = 0;
	for(int i = 1; i <= n; ++i)
	{
		A[i].w = read(), A[i].v = read(), A[i].c = read();
		V += A[i].v ;	
	}
	sort(A + 1, A + n + 1);
	for(int i = 1; i <= n; ++i) V -= W * A[i].c , W += A[i].w ;
	printf("%lld\n", V);
	return 0;
}

I 构造,二进制

发现对于第 \(i\) 位上 1,可以将其调整为 \(2^i = 2^{i + 1} - 2^{i}\) 的形式,即变为二进制位上的一个 1 一个 -1,则可将第 \(i+1\) 位上的 0 消去。

然后发现不断通过上述策略调整即可仅使用 1 和 -1 对原有的二进制位进行调整,消去原有的 0 进行构造。若无法构造说明无解,且发现此时一定是 4 的倍数,使得无论怎么构造最后两位的 0 都无法消去。

code by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
#define ll long long
#define ull unsigned long long

int read()
{
	int x = 0; bool f = false; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
	while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return f ? -x : x;
}
int n,T;
int a[33];
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
//	n=read();
	std::cin>>T;
	while(T--){
		cin>>n;
		for(int i=0;i<=31;++i){
			if((n>>i)&1) a[i]=1;
			else a[i]=0;
		}	
		for(int i=0;i<31;++i){
			if(a[i]==1&&a[i+1]==0) a[i+1]=1,a[i]=-1;	
		}
		bool fl=0;
		for(int i=0;i<31;++i){
			if(a[i]==0&&a[i+1]==0) fl=1;
		}
		if(fl) cout<<"NO\n";
		else{
			cout<<"YES\n";
//			cout<<a[31]<<endl;
			for(int i=1;i<=4;++i){
				for(int j=0;j<8;++j){
					cout<<a[(i-1)*8+j]<<' ';
				}
				cout<<'\n';
			}
		}
	}
	return 0;
}

L 数学,三分

dztlb 大神秒了我看都没看,上去给大神写了个整数三分就过了。

显然两种操作不会交替使用,因为先进行操作 1 后进行操作 2,发现本次操作 1 实际上是没有贡献的。则一定是先不断进行操作 2 直至某个阈值 \(c\),再不断进行操作 1 直至 0。

于是考虑枚举进行若干次操作 2 后的阈值 \(c\),求得第一次操作使得小于该阈值的期望步数。一次操作的概率为 \(p = \frac{c}{t}\),发现这是个典型的 \(r=1\) 的帕斯卡分布,仅需考虑不小于 \(k(k\ge 0)\) 次操作后使大于阈值的概率,考虑级数求和可知期望步数即:

\[p + p(1 - p) + p(1-p)^2 + \cdots = \frac{1}{p} = \frac{t}{c} \]

然后考虑操作 2 使得小于阈值后的取值在 \(0\sim c\) 中概率均等,则进行操作 1 的期望次数显然即:

\[\frac{1}{c+1}(0 + 1 + 2 + \cdots + c) = \frac{c-1}{2} \]

则对于某个阈值 \(c\) 的期望操作次数即为:

\[\frac{c-1}{2} + \frac{t}{c} \]

显然是个对勾函数的形式,可以直接解出或三分求得极值点。

code by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
#define ll long long
#define ull unsigned long long

int read()
{
	int x = 0; bool f = false; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
	while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return f ? -x : x;
}
int n;
int t;
double check(int k){
	return (double)(k-1.00)/2+(double)t/k;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
//	n=read();
	std::cin>>n;
	for(int i=1;i<=n;++i){
		cin>>t;
//		for(int j=1;j<=t;++j){
//			cout<<j<<' '<<check(j)<<'\n';
//		}
		int lmid, rmid;
		int id=1;
		double lans, rans,ans=check(1);
		for (int l = 1, r = t; l <= r; ) {
			lmid = l + (r - l + 1) / 3;
			rmid = r - (r - l + 1) / 3;
			lans = check(lmid);
			rans = check(rmid);
			if(ans>lans) id=lmid,ans=lans;
			if(ans>rans) id=rmid,ans=rans;
//			cout<<lans<<' '<<rans<<' '<<l<<' '<<r<<'\n';
			if (lans <= rans) r = rmid - 1; 
			else l = lmid + 1;
		}
		for(int j=min(lmid,rmid)-5;j<=max(lmid,rmid)+5;++j){
			if(j<1||j>t) continue;
			lans = check(j);
			if(ans>lans) id=j,ans=lans;
		}
//		id=13; 
		int x=id*(id-1)+2*t;
		int y=2*id;
		int gg=__gcd(x,y);
		x/=gg,y/=gg;
		cout<<x<<' '<<y<<'\n';
	}
	return 0;
}

G 数学,辗转相除

发现薯薯的总数单调递减,于是想到能否递归地计算。

显然仅需分别考虑 \(x>y\)\(x<y\) 的情况,然后可以分别写出两种情况的递推公式。发现每次递归均会使 \(x:=x-y\)\(y:=y-x\) 是一个辗转相减的形式,且都可以转化为等比数列的形式,于是考虑将同种的连续的辗转相减合并为辗转相除,然后套用等比数列公式即可。

code by wenqizhi:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
	int x = 0; bool f = false; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
	while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	return f ? -x : x;
}

const ll mod = 998244353;

ll qpow(ll a, ll b, ll mod)
{
	ll ans = 1;
	while(b)
	{
		if(b & 1) ans = ans * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return ans;
}

ll a0, a1, b, p0, p1, p2;



ll dfs(ll x, ll y)
{
	if(x == y) return p0 * p2 % mod;
	if(x < y)
	{
		int k = y / x;
		if(y % x == 0) --k;
		return dfs(x, y - k * x) * qpow(p0 * p2 % mod, k, mod) % mod;
	}else
	{
		int k = x / y;
		if(x % y == 0) --k;
		return ((dfs(x - k * y, y) - 1 + mod) % mod * qpow(p1 * p2 % mod, k, mod) % mod + 1) % mod;
	}
}

void solve()
{
	int x = read(), y = read();
	a0 = read(), a1 = read(), b = read();
	p0 = a0 * qpow(b, mod - 2, mod) % mod, p1 = a1 * qpow(b, mod - 2, mod) % mod, p2 = (1 - p0 - p1 + mod + mod) % mod;
	p2 = qpow((1 - p2 + mod) % mod, mod - 2, mod);
	printf("%lld\n", dfs(x, y));
}

int main() {
//	ios::sync_with_stdio(0);
//	cin.tie(0), cout.tie(0);
	int T = read();
	while(T--) solve();
	return 0;
}

E 结论,最短路

dztlb 大神秒了,我看都没看。

看起来就是简单题详见题解吧。

code by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
const int M=2e6+5;
const int inf=1e18;
#define ll long long
#define ull unsigned long long
int T,n,m,d,k;
int id[N][2];
int s[N];
int lim[N][2],tot,head[N*2];
int f[N][2],pre[N][2];
int p[N*4],L,R;
struct node{
	int to,nxt;
}e[M<<1];
void add(int u,int v){
	e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;
}
void get_new(bool fl,int now,int u,int v){
//	if(u==3){
//		cout<<"!!\n";
//		cout<<fl<<' '<<now<<' '<<u<<' '<<v<<endl;
//	}
	if(lim[u][fl]>now&&f[u][fl]==inf){
//		cout<<u<<endl;
		f[u][fl]=now;
		pre[u][fl]=v;
		if(fl) p[++R]=u+n;
		else p[++R]=u;
	}
}
int ans[N*4][2],la[2];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m>>d;
		for(int i=1;i<=n;++i) id[i][0]=i,id[i][1]=n+i;
		for(int i=0;i<=n;++i){
			f[i][0]=f[i][1]=lim[i][0]=lim[i][1]=inf;
			pre[i][0]=0;
			pre[i][1]=0;
		}
		tot=0;
		for(int i=1;i<=2*n;++i) head[i]=0;
		for(int i=1,u,v;i<=m;++i){
			cin>>u>>v;
			add(u,v),add(v,u);
//			add(id[u][0],id[v][1]);
//			add(id[u][1],id[v][0]);
//			add(id[v][1],id[u][0]);
//			add(id[v][0],id[u][1]);
		}
		cin>>k;
		for(int i=1;i<=k;++i){
			cin>>s[i];
			p[i]=id[s[i]][0];
			lim[s[i]][0]=0;
		}
		L=1,R=k;
		while(L<=R){
			int u=p[L];
			++L;
			bool fl=0;
			if(u>n) fl=1,u-=n;
//			cout<<u<<' '<<fl<<'\n';
			int now=lim[u][fl];
			if(now>=d) continue;
			for(int i=head[u];i;i=e[i].nxt){
				int v=e[i].to;
//				cout<<v<<"!!!\n";
//				cout<<lim[v][!fl]<<endl;
				if(lim[v][!fl]==inf){
					lim[v][!fl]=now+1;
					p[++R]=id[v][!fl];
				}
			}
		}
		// lim done
//		for(int i=1;i<=n;++i){
//			cout<<i<<' '<<lim[i][0]<<' '<<lim[i][1]<<'\n';
//		}
		L=1,R=0;
		get_new(0,0,1,0);
//		cout<<f[1][0]<<' '<<f[1][1]<<"askljdsjlak"<<endl;
		while(R>=L){
			int u=p[L];
			++L;
			bool fl=0;
			if(u>n) fl=1,u-=n;
//			cout<<u<<' '<<fl<<"!!!\n";
			for(int i=head[u];i;i=e[i].nxt){
				int v=e[i].to;
//				cout<<v<<"???????\n";
				int now=f[u][fl]+1;
				get_new(!fl,now,v,u);
			}
		}
		la[0]=la[1]=inf;
		for(int ty=0;ty<=1;++ty){
			int tmp=ty,now=n;
			if(f[n][ty]!=inf){
				la[ty]=1;
				while(now){
					ans[la[ty]][ty]=now;
					now=pre[now][tmp];
					++la[ty];
					tmp=!tmp;
					
				}
			}
		}
		if(la[0]==inf&&la[1]==inf){
			cout<<-1<<'\n';
			continue;
		}
		if(la[0]>la[1]){
			cout<<la[1]-2<<'\n';
			for(int i=la[1]-1;i>=1;--i){
				cout<<ans[i][1]<<' ';
			} cout<<'\n';
		}else{
			cout<<la[0]-2<<'\n';
			for(int i=la[0]-1;i>=1;--i){
				cout<<ans[i][0]<<' ';
			} cout<<'\n';
		}
	}
	return 0;	
}
/*
1
7 8 2
1 2
2 3
3 7
2 5
5 6
3 6
1 4
4 5
1 4
*/

C 字符串,kmp,结论

场上看到字符串就高超了在这题坐牢 2h 呃呃

以下为官方做法基础上的优化版本。

发现贡献的形式实际上即 Z 函数的定义。枚举每一个后缀 \(i\),求它与整个串的最长公共前缀 \([i, i+z_i-1]\),则其对答案的贡献即:\(B_{i}\times \sum_{j=i}^{i+z_i - 1} A_j\)

考虑每次添加字符 \(S_i\) 后计算答案的增量。发现添加字符后,对 \(z_1\sim z_i\) 的影响有如下四种情况:

  • \(j+z_j - 1 < i- 1\),则 \(z_j\) 已确定,之后的添加操作均无影响;
  • \(j+z_j - 1 = i - 1\),且 \(S_i \not= S_{z_j + 1}\),则会令 \(z_j\) 确定,之后的添加操作均无影响;
  • \(j+z_j - 1 = i - 1\),且 \(S_i = S_{z_j + 1}\),则 \(z_j:= z_j + 1\)
  • 特判是否有 \(S_1 = S_i\),若有则 \(z_i = 1\)

则本次答案的增量,即上述情况 3、4 的 \(B_j\) 之和乘 \(A_i\)。于是考虑维护有贡献的 \(B_j\) 之和,仅需每次在确定 \(z_j\) 的同时,减去情况 2 的 \(B_j\) 的贡献,再加上情况 4 的 \(B_j\) 的贡献即可。

然而强制在线显然不能直接跑 Z 函数,但是 KMP 支持末尾添加字符,于是考虑如何使用 KMP 实现上述功能。发现若满足 \(j+z_j - 1 = i - 1\),则 \(S[1:z_j]\) 一定是 \(i-1\) 的 border,于是仅需考虑枚举 \(i-1\) 的 border,并考虑下一个字符是否为 \(S_i\) 即可完成贡献的 \(B_j\) 之和的修改。

然而并不能直接暴跳 border 太呃呃了,考虑按照 border 的下一个字符的种类路径压缩一下,记 \(\operatorname{fa}_i\) 表示前缀 \(s[1:i]\) 的满足 \(s_{k+1}\not= s_{i+1}\) 的第一个 border \(s[1:p]\),于是每次仅需从 \(i-1\) 不断跳 \(\operatorname{fa}\)\(\operatorname{fail}\),并满足将 \(s_{p+1}\not= s_{i+1}\) 的 border 的贡献 \(B_{i-p}\) 减去即可。

可以保证每个 \(B_i\) 仅会在贡献中被加一次减一次,由字符串的性质可知所有跳 \(\operatorname{fa}\) 的次数加起来是线性级别的,则暴跳复杂度是正确的。

实现详见代码,总时间复杂度 \(O(n)\) 级别。

#include <bits/stdc++.h>

#define LL long long

const int kN = 5e5 + 10;
int n;
int S[kN], A[kN], B[kN];
int fail[kN], fa[kN], vis[kN];

void solve() {
	LL ans = 0, sumb = 0;
  fail[1] = 0;
	for (int i = 1, j = 0; i <= n; ++ i) {
		std::cin >> S[i] >> A[i] >> B[i];
		S[i] = (1ll * ans + S[i]) % n;
		if (i > 1) {
      fa[i - 1] = S[fail[i - 1] + 1] == S[i] ? fa[fail[i - 1]] : fail[i - 1];
			while (j && S[i] != S[j + 1]) j = fail[j];
			if (S[j + 1] == S[i]) ++ j;
			fail[i] = j;
		}
		
    if (S[i] == S[1]) sumb += B[i];
		int p = i - 1;
    while (p) {
      int q = fa[p];
      if (S[p + 1] != S[i]) {
        while (p != q) sumb -= B[i - p], p = fail[p];
      }
      p = q;
    }
		ans += 1ll * sumb * A[i];
		
		std::cout << ans << "\n";
	}
}

int main() {
  // freopen("1.txt", "r", stdin);
	std::ios::sync_with_stdio(0);
	std::cin.tie(0), std::cout.tie(0);
	std::cin >> n;
	solve();
	return 0;
}
/*
6
0 1 1
1 2 2
0 3 3
0 4 4
1 5 5
0 6 6
*/

写在最后

感觉这场一直在摸鱼呃呃队友太强了都。

学到了什么:

  • L:期望的线性性。
  • G:辗转相减 \(\iff\) 辗转相除;
posted @ 2024-09-21 21:56  Luckyblock  阅读(660)  评论(0编辑  收藏  举报