【题解】gym103743 (2022 JSCPC)

A. PENTA KILL!

考虑直接模拟,规则就是一个人将其他人全部都击杀,并且中间没有重复击杀。

code:

#include<bits/stdc++.h>
using namespace std;
map<string , vector<string> > st;

int n;
string a,b;

int main(){
    cin >> n;
    for(int i = 1 ; i <= n ; ++i){
        cin >> a >> b;
        st[a].push_back(b);
    }
    for(auto t : st){
    	auto e = t.second;
        for(int i = 4; i < e.size(); ++i){
            set<string> s;
            s.insert(e[i]);
            s.insert(e[i-1]);
            s.insert(e[i-2]);
            s.insert(e[i-3]);
            s.insert(e[i-4]);
            if(s.size() == 5){
                cout << "PENTA KILL!";
                return 0;
            }
        }
    }
    cout << "SAD:(";
}

B. Prime Ring Plus

\(1 \sim n\) 分成若干个环,每个环上相邻两数之和是质数

我们考虑可以网络流流流。

我们考虑因为一条边连接的两个点的和是素数,大于 \(2\) 的素数都是奇数,所以说一条边链接的两个数一定是一奇一偶。

那么连出来的图显然是一个二分图。现在我们的目标就是要让这个二分图中的每一个点有且仅有两个匹配(环中一个点链接两个点)。

我们考虑可以网络流求解,建图方式如下:

  • 源点 \(S\) 向所有奇数点连边,流量为 \(2\)
  • 奇数点向偶数点(在奇+偶=质数的情况下)连边,流量为 \(1\)
  • 偶数点向汇点 \(T\) 连边,流量为 \(2\)

其中橙色边流量为 \(2\),绿色边流量为 \(1\)

我们可以发现只要跑出来的网络流满流,那么一定就可以构造出一组解。(中间奇数点向偶数点的边剩余流量为 \(0\),那么一定被选作了环中的其中一条边)

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 4e4 + 8,MM = 6e6 + 8,INF = 0x3f3f3f3f;
int n;
int S,T;

inline int read(){
	register char c = getchar();
	register int res = 0;
	while(!isdigit(c)) c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0', c = getchar();
	return res;
}

struct Edge{
	int to,next,val;
}edge[MM << 1];
int head[NN], pre[NN], cnt;
void add_edge(int u,int v,int w){
	edge[++cnt] = {v,head[u],w};
	pre[u] = head[u] = cnt;
	swap(u,v);
	edge[++cnt] = {v,head[u],0};
	pre[u] = head[u] = cnt;
} 

bool pis[NN];
int prime[NN],cntp;
void init(){
	memset(head,-1,sizeof(head));
	memset(pre,-1,sizeof(pre));
	cnt = 1;
	int m = n * 2;
	for(int i = 2; i <= m; ++i){
		if(!pis[i]) prime[++cntp] = i;
		for(int j = 1; j <= cntp; ++j){
			if(i * prime[j] > m) break;
			pis[i * prime[j]] = 1;
			if(i % prime[j] == 0) break;
		}
	}
}

int dis[NN];
bool bfs(){
	memset(dis,0,sizeof(dis));
	queue<int> q;
	q.push(S);dis[S] = 1;head[S] = pre[S];
	while(!q.empty()){
		int u = q.front();q.pop();
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].to,val = edge[i].val;
			if(val && !dis[v]){
				dis[v] = dis[u] + 1;
				head[v] = pre[v];
				q.push(v);
				if(v == T) return 1;
			}
		}
	}
	return 0;
}
int dinic(int u,int flow){
	if(u == T) return flow;
	int res = flow;
	for(int &i = head[u]; i != -1; i = edge[i].next){
		int v = edge[i].to,val = edge[i].val;
		if(val && dis[u] + 1 == dis[v]){
			int k = dinic(v,min(flow,val));
			if(!k) dis[v] = 0;
			edge[i].val -= k; edge[i^1].val += k;
			res -= k;
		}
		if(res == 0) break;
	}
	return flow - res;
}

int gt[NN][2];
vector<vector<int> > ansf;
bool vis[NN];

int main(){
	n = read();
	if(n & 1) return puts("-1"),0;
	init();
	S = n+1; T = n+2;
	for(int i = 1; i <= n; i += 2) add_edge(S,i,2);
	for(int i = 2; i <= n; i += 2) add_edge(i,T,2);
	for(int i = 1; i <= n; i += 2)
		for(int j = 2; j <= n; j += 2)
			if(!pis[i+j]) add_edge(i,j,1);
	
	int ans = 0,flow = 0;
	while(bfs()){
		if(flow = dinic(S,INF)) ans += flow;
	}
	
	if(ans != n) return puts("-1"),0;
	
	for(int u = 1; u <= n; u += 2){
		head[u] = pre[u];
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].to;
			if(v == S) continue;
			if(edge[i].val == 0){
				if(!gt[u][0]) gt[u][0] = v;
				else gt[u][1] = v;
				if(!gt[v][0]) gt[v][0] = u;
				else gt[v][1] = u;
			}
		}
	}
	
	for(int i = 1; i <= n; i += 2){
		if(vis[i]) continue;
		vector<int> r;
		int now = i, fm = 0;
		while(now != i || fm == 0){
			int x = now;
			vis[now] = 1;
			r.push_back(now);
			if(fm == gt[now][0]) now = gt[now][1];
			else now = gt[now][0];
			fm = x;
		}
		ansf.push_back(r);
	}
	
	printf("%d\n",ansf.size());
	for(int i = 0; i < ansf.size(); ++i){
		printf("%d",ansf[i].size());
		for(auto j : ansf[i]){
			printf(" %d",j);
		}
		puts("");
	}
}

C. Jump and Treasure

DP 待补

D. Finding Pairs

给定一个序列和一个数 \(k\),每次询问一个区间,问在区间中找若干对不含重复元素的距离为 \(k\) 的数的最大权值和、

我们考虑 \(\bmod k\) 不同的位置互相不影响,所以说我们可以先把所有位置按 \(\bmod k\) 分组。

我们对于每一组分别考虑:

我们可以想到一个 DP 思路,设 \(f_{i,j,0/1,0/1}\) 表示区间 \([i,j]\) 最左边是否取,最右边是否取,得到的最大权值和

这个 DP 的转移方程显然是简单的,就是从 \([i,j-1]\)\([i+1,j]\) 转移一下即可,具体可以看代码。

然后我们考虑我们的询问显然是不到 \(n^2\) 的,所以 DP 会计算很多不需要的状态,我们可以 分块/回滚莫队 解决这道题。

下面是回滚莫队代码(分块没写):

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 1e5 + 8, BB = 4e3 + 8, INF = 1e18;
ll n,k,q;
ll a[NN],b[NN],c[NN];

inline ll read(){
	register ll res = 0,flag = 1;
	register char c = getchar();
	while(!isdigit(c)) flag = c == '-' ? -1 : flag,c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0', c = getchar();
	return res * flag;
}

int len,cntb;
int block[NN];

struct Query{
	int l,r,id;
	bool operator < (const Query &x) const{
		if(block[l] == block[x.l]) return r < x.r;
		return l < x.l;
	}
};
vector<Query>Q[BB];
ll ans[NN];

ll g[BB][2];
ll f[BB][2][2];

ll solve(int l,int r){
	ll res = 0;
	for(int i = l; i <= r; ++i){
		if(i - l < k) g[b[i]][0] = 0,g[b[i]][1] = -INF;
		ll pre = g[b[i]][0];
		res -= g[b[i]][0];
		g[b[i]][0] = max(g[b[i]][0],g[b[i]][1] + a[i]);
		res += g[b[i]][0];
		g[b[i]][1] = max(-INF,pre + a[i]);
	}
	return res;
}

ll res;
bool vis[NN];

void addl(int x){
	if(!vis[b[x]]){
		f[b[x]][0][1] = max(-INF,a[x]);
		f[b[x]][1][0] = max(-INF,a[x]);
		vis[b[x]] = 1;
	}
	else{
		ll num0 = f[b[x]][0][0],num1 = f[b[x]][0][1];
		res -= f[b[x]][0][0];
		f[b[x]][0][0] = max(f[b[x]][0][0],f[b[x]][1][0] + a[x]);
		res += f[b[x]][0][0];
		f[b[x]][1][0] = max(-INF,num0 + a[x]);
		f[b[x]][0][1] = max(f[b[x]][0][1],f[b[x]][1][1] + a[x]);
		f[b[x]][1][1] = max(-INF,num1 + a[x]);
	}
}
void addr(int x){
	if(!vis[b[x]]){
		f[b[x]][0][1] = max(-INF,a[x]);
		f[b[x]][1][0] = max(-INF,a[x]);
		vis[b[x]] = 1;
	}
	else{
		ll num0 = f[b[x]][0][0],num1 = f[b[x]][1][0];
		res -= f[b[x]][0][0];
		f[b[x]][0][0] = max(f[b[x]][0][0],f[b[x]][0][1] + a[x]);
		res += f[b[x]][0][0];
		f[b[x]][0][1] = max(-INF,num0 + a[x]);
		f[b[x]][1][0] = max(f[b[x]][1][0],f[b[x]][1][1] + a[x]);
		f[b[x]][1][1] = max(-INF,num1 + a[x]);
	}
	
}

ll sta[NN],num[NN][2][2],nvis[NN],top;

int main(){
	n = read(); k = read(); q = read();
	
	len = sqrt(n);
	for(int i = 1; i <= n; ++i) block[i] = (i-1) / k + 1;
	cntb = block[n];
	for(int i = 0; i < k; ++i){
		f[i][0][0] = f[i][1][0] = 0;
		f[i][0][1] = f[i][1][1] = -INF;
	}
	
	for(int i = 1; i <= n; ++i) a[i] = read(),b[i] = i % k,c[i] = (i-1) / k;
	for(int i = 1; i <= q; ++i){
		int l = read(), r = read();
		Q[block[l]].push_back({l,r,i});
	}
	for(int i = 1; i <= cntb; ++i)
		sort(Q[i].begin(),Q[i].end());
	
	for(int i = 1; i <= cntb; ++i){
		int r = i * len, l = r + 1;
		res = 0;
		for(auto arr : Q[i]){
			int ql = arr.l,qr = arr.r;
			if(block[ql] == block[qr]){
				ans[arr.id] = solve(ql,qr);
				continue;
			}
			while(r < qr) addr(++r);
			ll x = res;
			for(int j = ql; j <= l; ++j)
				sta[++top] = b[j],
				num[top][0][0] = f[b[j]][0][0],
				num[top][0][1] = f[b[j]][0][1],
				num[top][1][0] = f[b[j]][1][0],
				num[top][1][1] = f[b[j]][1][1],
				nvis[top] = vis[b[j]];
			while(l > ql) addl(--l);
			ans[arr.id] = res;
			while(top){
				f[sta[top]][0][0] = num[top][0][0],
				f[sta[top]][0][1] = num[top][0][1],
				f[sta[top]][1][0] = num[top][1][0],
				f[sta[top]][1][1] = num[top][1][1];
				vis[sta[top]] = nvis[top];
				--top;
			}
			res = x;l = i * len + 1;
		}
		for(int j = (i-1)*len+1; j <= i*len; ++j)
			f[b[j]][0][0] = 0,
			f[b[j]][1][0] = f[b[j]][0][1] = f[b[j]][1][1] = -INF,
			vis[b[j]] = 0;
	}
	
	for(int i = 1; i <= q; ++i){
		printf("%lld\n",ans[i]);
	}
}

E Playing Cards

AliceBob 在玩游戏,每个人都有 \(n\) 张牌,每张牌上有一个数字 \(x\),每轮游戏双方都分别出一张牌,最后点数大的一方获胜。

现在 Alice 知道 Bob 的出牌顺序,并且 Alice 有魔法可以让一张牌的点数 \(+k\),求 \(Alice\) 最少需要施展多少次魔法才能在每一局中获胜?

输出魔法次数和方案

我们考虑如果说 Alice 施展完魔法,那么 Alice 的出牌顺序一定是和 Bob 出的牌的大小位次相同(即若 Bob 从大到小出牌,那么 Alice 也从大到小出牌)

我们考虑先对 Bob 的牌从小到大排序。

我们考虑有 \(n^2\log n\) 的做法,就是说我们从小到大去赢 Bob 的所有牌,如果说当前 Alice 的最小的牌比 Bob 的小,那么就施展魔法知道刚好超过 Bob 的这张牌,我们再重新对 Alice 的牌排序,继续做这个操作,直到 Alice 的牌更大,那么我们就可以将这两个牌分别移除。

我们考虑上面的做法为什么会这么劣,发现因为有可能一张 Alice 的牌可能会一直被施展魔法,直到最后最劣被施展 \(n\) 次魔法。

我们考虑如何一步到位,我们考虑对于 Bob 的第 \(i\) 张牌 \(b_i\),我们将 Alice 所有小于 \(b_i+k-1\) 的没有被删除的牌加入一个堆。

我们会发现,这个堆中的所有数如果和 \(b_i\) 配对,浪费的点数不会超过 \(k-1\),不在堆中的书显然会更劣。

然后我们每次再在堆中找到浪费点数最小的牌,具体地,可以将牌 \(a_i\) 在推入堆时放入二元组 (\(a_i \bmod k\),i),然后在查询时 lower_bound() 进行查询即可,然后再通过堆中记录的对应下标计算需要使用的魔法次数即可。

注:代码的思路与上文相同,但是将问题反了一下,变成了从大到小匹配,一次魔法给 Bob 的牌 \(-k\)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 1e5 + 8, BB = 4e3 + 8, INF = 1e18;
ll n,k,q;
ll a[NN],b[NN],c[NN];

inline ll read(){
	register ll res = 0,flag = 1;
	register char c = getchar();
	while(!isdigit(c)) flag = c == '-' ? -1 : flag,c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0', c = getchar();
	return res * flag;
}

int len,cntb;
int block[NN];

struct Query{
	int l,r,id;
	bool operator < (const Query &x) const{
		if(block[l] == block[x.l]) return r < x.r;
		return l < x.l;
	}
};
vector<Query>Q[BB];
ll ans[NN];

ll g[BB][2];
ll f[BB][2][2];

ll solve(int l,int r){
	ll res = 0;
	for(int i = l; i <= r; ++i){
		if(i - l < k) g[b[i]][0] = 0,g[b[i]][1] = -INF;
		ll pre = g[b[i]][0];
		res -= g[b[i]][0];
		g[b[i]][0] = max(g[b[i]][0],g[b[i]][1] + a[i]);
		res += g[b[i]][0];
		g[b[i]][1] = max(-INF,pre + a[i]);
	}
	return res;
}

ll res;
bool vis[NN];

void addl(int x){
	if(!vis[b[x]]){
		f[b[x]][0][1] = max(-INF,a[x]);
		f[b[x]][1][0] = max(-INF,a[x]);
		vis[b[x]] = 1;
	}
	else{
		ll num0 = f[b[x]][0][0],num1 = f[b[x]][0][1];
		res -= f[b[x]][0][0];
		f[b[x]][0][0] = max(f[b[x]][0][0],f[b[x]][1][0] + a[x]);
		res += f[b[x]][0][0];
		f[b[x]][1][0] = max(-INF,num0 + a[x]);
		f[b[x]][0][1] = max(f[b[x]][0][1],f[b[x]][1][1] + a[x]);
		f[b[x]][1][1] = max(-INF,num1 + a[x]);
	}
}
void addr(int x){
	if(!vis[b[x]]){
		f[b[x]][0][1] = max(-INF,a[x]);
		f[b[x]][1][0] = max(-INF,a[x]);
		vis[b[x]] = 1;
	}
	else{
		ll num0 = f[b[x]][0][0],num1 = f[b[x]][1][0];
		res -= f[b[x]][0][0];
		f[b[x]][0][0] = max(f[b[x]][0][0],f[b[x]][0][1] + a[x]);
		res += f[b[x]][0][0];
		f[b[x]][0][1] = max(-INF,num0 + a[x]);
		f[b[x]][1][0] = max(f[b[x]][1][0],f[b[x]][1][1] + a[x]);
		f[b[x]][1][1] = max(-INF,num1 + a[x]);
	}
	
}

ll sta[NN],num[NN][2][2],nvis[NN],top;

int main(){
	n = read(); k = read(); q = read();
	
	len = sqrt(n);
	for(int i = 1; i <= n; ++i) block[i] = (i-1) / k + 1;
	cntb = block[n];
	for(int i = 0; i < k; ++i){
		f[i][0][0] = f[i][1][0] = 0;
		f[i][0][1] = f[i][1][1] = -INF;
	}
	
	for(int i = 1; i <= n; ++i) a[i] = read(),b[i] = i % k,c[i] = (i-1) / k;
	for(int i = 1; i <= q; ++i){
		int l = read(), r = read();
		Q[block[l]].push_back({l,r,i});
	}
	for(int i = 1; i <= cntb; ++i)
		sort(Q[i].begin(),Q[i].end());
	
	for(int i = 1; i <= cntb; ++i){
		int r = i * len, l = r + 1;
		res = 0;
		for(auto arr : Q[i]){
			int ql = arr.l,qr = arr.r;
			if(block[ql] == block[qr]){
				ans[arr.id] = solve(ql,qr);
				continue;
			}
			while(r < qr) addr(++r);
			ll x = res;
			for(int j = ql; j <= l; ++j)
				sta[++top] = b[j],
				num[top][0][0] = f[b[j]][0][0],
				num[top][0][1] = f[b[j]][0][1],
				num[top][1][0] = f[b[j]][1][0],
				num[top][1][1] = f[b[j]][1][1],
				nvis[top] = vis[b[j]];
			while(l > ql) addl(--l);
			ans[arr.id] = res;
			while(top){
				f[sta[top]][0][0] = num[top][0][0],
				f[sta[top]][0][1] = num[top][0][1],
				f[sta[top]][1][0] = num[top][1][0],
				f[sta[top]][1][1] = num[top][1][1];
				vis[sta[top]] = nvis[top];
				--top;
			}
			res = x;l = i * len + 1;
		}
		for(int j = (i-1)*len+1; j <= i*len; ++j)
			f[b[j]][0][0] = 0,
			f[b[j]][1][0] = f[b[j]][0][1] = f[b[j]][1][1] = -INF,
			vis[b[j]] = 0;
	}
	
	for(int i = 1; i <= q; ++i){
		printf("%lld\n",ans[i]);
	}
}

F. Pockets

\(n\) 种物品和一个容纳重量为 \(k\) 的背包,每种物品有价值 \(v\) 和重量 \(w\) 两种属性,无限个.
你可以购买至多 \(m\) 次,每次购买一个任意种物品,如果购买了 \(i\) 个,最后重量不能超过 \(k + i\).
一种方案的价值是物品价值之积,求所有方案价值之和 \(\bmod 998244353\).

我们考虑一次购买的生成函数:

\[f(x) = \sum v_ix^{w_i} \]

我们枚举买的次数 \(i\),可以得到答案:

\[\sum_{i=0}^m\sum_{j=0}^{i+k}[x^j]f^i(x) \]

posted @ 2024-01-15 21:02  ricky_lin  阅读(34)  评论(0编辑  收藏  举报