Loading

21.10 杂题

agc019b

二分答案,记 \(cnt_{i}\) 代表目前第 \(i\) 项活动作为多少个人的首选,每次把不能选的活动去除,最后如果所有人都满意了就结束。复杂度 \(O(nm\log{n})\)

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn = 305;
template <typename T>
void read(T &x) {
	T flag = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
	for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	x *= flag;
}
int n, m, a[maxn][maxn], cnt[maxn], rk[maxn];
bool vis[maxn];
vector<int> vec[maxn];
bool check(int mid) {
	int alive = m;
	for (int i = 1; i <= m; i++) cnt[i] = 0, vis[i] = 1, vec[i].clear();
	for (int i = 1; i <= n; i++) cnt[a[i][1]]++, vec[a[i][rk[i] = 1]].pb(i);
	while (1) {
		bool flag = 0;
		for (int j = 1; j <= m; j++) {
			if (vis[j]) {
				if (cnt[j] > mid) {
					vis[j] = 0;
					flag = 1;
					alive--;
					for (int k : vec[j]) {
						cnt[j]--;
						while (rk[k] < m && !vis[a[k][rk[k]]])rk[k]++;
						if (!vis[a[k][rk[k]]]) return 0;
						vec[a[k][rk[k]]].pb(k);
						cnt[a[k][rk[k]]]++;
					}
				}
			}
		}
		if (!flag && alive) return 1;
		if (!alive) return 0;
	}
	return 0;
}
int main() {
	read(n); read(m);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			read(a[i][j]);
	int l = 1, r = n, ans = n;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf("%d\n", ans);
	return 0;
}

然后仔细思考了一下我们这个过程,好像二分是不必要的。直接贪心,每次把出现次数最大的那个干掉,然后更新答案即可。但是效率差得不多。

CF550D

根据奇偶性分析一下发现当 \(k\) 是偶数时必定无解。考虑奇数时的构造,我们考虑构造两个完全相同的图然后两边连一下。显然造一个 \(k+2\) 个点的完全图然后把 \((4,5),(6,7)...(k+1,k+2),(1,2),(1,3)\)。 删掉即可。

AGC010E

对于不互质的点对后手显然不能改变他们的相对关系,所以对于两个不互质的点对 \((i,j)\) 将小的连向大的,最后拓扑排序即可,而由于后手可以改变互质的数的相对位置,每次取出最大的数即可。

然后这个东西假了。。。


看了下题解,发现不能直接那么定向,因为后手其实还是有办法的。

对于每个连通块,我们保存一棵生成树,每个点向没到过的点从小到大依次连边,并递归。这样做的好处是让 \(u\) 的两个儿子 \(v_1,v_2,a_{v_1}<a_{v_2}\) 不一定都连向 \(u\),因为这个时候如果 \(v_2\) 能排在 \(v_1\) 后面的话(即 \(v_1,v_2\) 也互质)那么显然更优。

#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair 
using namespace std;
typedef long long ll;
const int maxn = 2005, mod = 998244353;
template <typename T>
void read(T &x) {
	T flag = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
	for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	x *= flag;
}
int N, d[maxn];
ll A[maxn];
priority_queue<pair<int, int>> q;
vector<int> vec[maxn], G[maxn];
bool vis[maxn];
void dfs(int u) { vis[u] = 1;
	for (int v : vec[u])
		!vis[v] && (G[u].pb(v), d[v]++, dfs(v), 1);
}
int main() {
	read(N);
	for (int i = 1; i <= N; i++) read(A[i]);
	sort(A + 1, A + 1 + N);
	for (int i = 1; i <= N; i++)
		for (int j = i + 1; j <= N; j++)
			if (__gcd(A[i], A[j]) > 1)
				vec[i].pb(j), vec[j].pb(i);
	for (int i = 1; i <= N; i++) !vis[i] && (dfs(i), 1);
	for (int i = 1; i <= N; i++) d[i] == 0 && (q.push(mp(A[i], i)), 1);
	while (!q.empty()) {
		int u = q.top().second; q.pop();
		printf("%lld ", A[u]);
		for (int v : G[u])
			d[v]--, d[v] == 0 && (q.push(mp(A[v], v)), 1);
	}
	return 0;
}

CF542E

考虑一个图如果有一个奇环,那么手玩一下发现是无解。然后考虑分层,于是答案就是最远点对的距离。

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn = 1005;
template <typename T>
void read(T &x) {
	T flag = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
	for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 +ch - '0';
	x *= flag;
}
int n, m, col[maxn], bel[maxn], cnt, mx[maxn], ans, d[maxn];
queue<int> q;
vector<int> vec[maxn];
bool dfs(int u, int c) {
	col[u] = c; bool ret = 1; bel[u] = cnt;
	for (int v : vec[u]) col[v] == -1 ? ret &= dfs(v, c ^ 1) : ret &= col[v] != col[u];
	return ret;
}
int bfs(int s) {
	memset(d, -1, sizeof(d));
	d[s] = 0; q.push(s); int ret = 0;
	while (!q.empty()) {
		int u = q.front(); q.pop();
		ret = max(ret, d[u]);
		for (int v : vec[u]) d[v] == -1 && (d[v] = d[u] + 1, q.push(v), 1);
	}
	return ret;
}
int main() {
	read(n); read(m); for (int i = 1; i <= n; i++) col[i] = -1;
	for (int i = 1, u, v; i <= m; i++) {
		read(u); read(v);
		vec[u].pb(v); vec[v].pb(u);
	}
	for (int i = 1; i <= n; i++) {
		if (col[i] == -1) {
			cnt++;
			if (!dfs(i, 0)) return puts("-1"), 0;
		}
	}
	for (int i = 1; i <= n; i++) mx[bel[i]] = max(mx[bel[i]], bfs(i));
	for (int i = 1; i <= cnt; i++) ans += mx[i];
	printf("%d\n", ans);
	return 0;
}

CF1458E

先不考虑特殊点,考虑把两堆石子的状态看成坐标轴上的点,那么每一行每一列只有一个非法状态,考虑把所有非法状态处理出来,用意发现这是一条 \(45\) 度的斜线。考虑它碰到了一个特殊点,假设这个特殊点在他左边,也就是说它从下面撞上了这个特殊点引出来的线平行于 x 轴的那一条,这相当于对其做了次向上偏移。我们考虑维护这个折线,有一种很好的写法是把截距相同的一段直接记在map里。具体实现可以参考代码(我也是参考别人的)

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9;
const int MAXN=100005;
template <typename T>
void read(T &x) {
	T flag=1;
	char ch=getchar();
	for (; '0'>ch||ch>'9'; ch=getchar()) if (ch=='-') flag=-1;
	for (x=0; '0'<=ch&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
	x*=flag;
}
struct node{
	int x, y;
}p[MAXN];
int n, m;
map<int, int> mnx, mny;
map<pair<int, int>, bool> mark;
map<int, map<int, int> > mp;
int main() {
	read(n); read(m);
	for (int i=1, x, y; i<=n; i++) {
		read(x); read(y);
		mark[make_pair(x, y)]=true;
		if (mnx.find(x)==mnx.end()) mnx[x]=y;
		else mnx[x]=min(mnx[x], y);
		if (mny.find(y)==mny.end()) mny[y]=x;
		else mny[y]=min(mny[y], x);
	}
	int nowx=0, nowy=0;
	for (; nowx<=inf&&nowy<=inf; ) {
		if (mnx.find(nowx)!=mnx.end()&&mnx[nowx]<=nowy) {
			nowx++;
			continue;
		}
		if (mny.find(nowy)!=mny.end()&&mny[nowy]<=nowx) {
			nowy++;
			continue;
		}
		if (mnx.find(nowx)!=mnx.end()||mny.find(nowy)!=mny.end()) {
			mark[make_pair(nowx, nowy)]=true;
			nowx++;
			nowy++;
			continue;
		}
		map<int, int> :: iterator x=mnx.lower_bound(nowx), y=mny.lower_bound(nowy);
		int tmp=inf;
		if (x!=mnx.end()) tmp=min(tmp, (*x).first-nowx);
		if (y!=mny.end()) tmp=min(tmp, (*y).first-nowy);
		mark[make_pair(nowx, nowy)]=true;
		mp[nowx-nowy][nowx+tmp]=nowx;
		nowx+=tmp;
		nowy+=tmp;
	}
	for (int i=1; i<=m; i++) {
		int a, b;
		read(a); read(b);
		if (mark[make_pair(a, b)]) puts("LOSE");
		else {
			map<int, int> :: iterator it=mp[a-b].upper_bound(a);
			if (it!=mp[a-b].end()&&(*it).second<=a) {
				puts("LOSE");
			} else {
				puts("WIN");
			}
		}
	}
	return 0;
}//kel

CF1147F

这道题有一个关键是第一步不需要考虑前面的限制,那么我们猜想先手(B)不知道怎么输。考虑任意取出一组最大匹配中的两条匹配边 \((u,v),(p,q)\),不妨设先手选择的是 Increasing,那么对于 \(w(v,p)>w(u,v)\)\(w(p,q)<w(v,p)\) 那么这组匹配被称作好的。对于一组好的匹配,我们发现后手只要走匹配边即可。下面证明一定存在好的匹配,我们只需找出这组好的匹配即可。如果 \(\exists w(u,v)<w(v,p)<w(p,q)\),我们将这组匹配改为 \((p,v),(u,q)\) 即可变成一组好的,容易发现这就是我们在做稳定婚姻问题是更改匹配边的过程,所以我们对左侧点定义满意度为从小到大,右侧点为从大到小跑稳定婚姻问题即可。

#include <bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define F first
#define S second
const int maxn=105;
template<typename T>
void read(T &x){
	T flag=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
	for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	x*=flag;
}
int n,a[maxn][maxn],rnk[maxn][maxn],pos[maxn],nxt[maxn],p;
std::priority_queue<pii>pq;
std::queue<int>q;
void solve(){
	read(n);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)read(a[i][j]);
	putchar('B');putchar('\n');
	fflush(stdout);
	char ch;scanf("%c",&ch);
	if(ch=='D')for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=-a[i][j];
	read(p);
	if(p>n)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=-a[i][j];
	for(int i=1;i<=n;i++){
		q.push(i);
		for(int j=1;j<=n;j++)pq.push(mp(-a[i][j],j+n));
		int tmp=0;
		while(!pq.empty()){
			int u=pq.top().S;pq.pop();
			rnk[i][++tmp]=u;
		}
	}
	for(int i=1;i<=n+n;i++)nxt[i]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		if(!u)continue;
		for(int i=1;i<=n&&!nxt[u];i++){
			int w=rnk[u][i];
			if(!nxt[w]||a[u][w-n]>a[nxt[w]][w-n])nxt[nxt[w]]=0,q.push(nxt[w]),nxt[u]=w,nxt[w]=u;
		}
	}
	while(1){
		if(p==-1||p==-2)break;
		printf("%d\n",nxt[p]);fflush(stdout);
		read(p);
	}
}
int main(){
	int T;scanf("%d",&T);
	while(T--)solve();
	return 0;
}

CF1033

这道题得发现一个看起来很套路但又不太敢相信它是对的的套路。设当前局面为 \(S\),定义 \(S'\) 为每个 \(v'\to v \bmod (a+b)\) 后的局面,则 \(S\)\(S'\) 的赢家是同样的人。

证明咕。

然后考虑计数,当 A 或 B 必胜时只与 \((a,b)\) 有关,所以令 \(a'=b,b'=a\) 那么胜负关系就反转了,所以 A 胜的方案一定与 B 胜的方案相同,所以我们只需要求出先手胜和后手胜的方案。注意每个 \(v'_i<a+b\)

#include<bits/stdc++.h>
#define pb push_back
#define mp std::make_pair
#define pii std::pair<int,int>
#define F first
#define S second
typedef long long ll;
typedef unsigned long long ull; 
namespace _name{
	const int maxn=105,mod=998244353,inf=0x3f3f3f3f;
	template<typename T>
	inline void read(T &x){
		T flag=1;
		char ch=getchar();
		for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
		for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
		x*=flag;
	}
	template<typename T>
	inline void write(T x){
		if(x==0)return putchar('0'),void();
		int stk[20],top=0;
		while(x)stk[++top]=x%10,x/=10;
		for(int i=top;i;i--)putchar(stk[i]+'0');
	}
	template<typename T>
	inline void print(T x,char End='\n'){
		if(x<0)x=-x,putchar('-');
		write(x);putchar(End);
	}
	inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
	inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
	inline int mul(int a,int b){return 1ll*a*b%mod;}
	inline int ksm(int a,int b=mod-2){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
}using namespace _name;
int n;
ll m,v[maxn],w[maxn],ans[2];
int main(){
	read(n);read(m);for(int i=1;i<=n;i++)read(v[i]);
	for(int s=2;s<=2*m;s++){
		for(int i=1;i<=n;i++)w[i]=v[i]%s;
		std::sort(w+1,w+1+n,[](const int a,const int b){return a>b;});
		w[0]=s-1;
		for(int i=0,id=0;i<=n;i++,id^=1){
			int l=std::max(w[i+1],w[id+1]/2)+1,r=std::min(m,w[i]);
			ans[id]+=std::max(0,std::min(r,s-l)-std::max(l,s-r)+1);
		}
	}
	print((1ll*m*m-ans[0]-ans[1])/2,' ');print((1ll*m*m-ans[0]-ans[1])/2,' ');print(ans[1],' ');print(ans[0]);
	return 0;
}

AGC010D

如果存在一个 \(1\) 那么只用看其他数的和的奇偶性。否则如果没有偶数那么后手只要模仿先手即可,先手必败。所以如果只有一个偶数那么先手必胜。如果有奇数个偶数,注意到不可能全是偶数(因为一开始的 \(\gcd\)\(1\)),那么先手可以保证一直都是奇数个偶数,最后一定会变成 \(1\) 个偶数,先手必胜。考虑偶数的偶数,因为先手想赢,他只有一种办法,那就是只有一个奇数,然后给这个奇数-1,剩下全除以 \(\gcd\),这种情况直接递归去做即可。

#include <bits/stdc++.h>
using namespace std;
int n;
int a[100005], cnt;
long long sum;
int ans;
void work(int id) {
	sum=0;
	cnt=0; 
	for (int i=1; i<=n; i++) {
		if (a[i]==1) {
			for (int j=1; j<=n; j++) sum+=a[j]-1;
			if(sum&1) ans=id;
			else ans=1-id;
			return;
		}
		if(!(a[i]&1)) cnt++;
	}
	if (cnt&1) ans=id;
	else {
		if (n-cnt>1) ans=1-id;
		else {
			int g=0;
			for (int i=1; i<=n; i++) if (a[i]&1) a[i]--;
			for (int i=1; i<=n; i++) g=__gcd(g, a[i]);
			for (int i=1; i<=n; i++) a[i]/=g;
			work(1-id);
		}
	}
	
}
int main() {
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%d", a+i);
	work(1);
	if (ans) puts("First");
	else puts("Second");
	return 0;
}

AGC026F

对于 \(n\) 为偶数的情况,通过反证法可以得知一定选左右开始。
对于奇数情况,由上可知一定选一个偶数的位置,然后后手会得到主动权然后递归去做,最后先手选了奇数的一段
考虑先全部选偶数,然后把一段改为奇数,这可以通过前缀和 \(+s[r]-s[l-1]\) 实现。
直接二分答案,然后求出合法区间,要求这些合法区间必须首位相连,即 \(r_{i}+1=l_{i+1}-1\)
我们可以使用一个 dp 判断,设 \(dp_{i}\) 代表 [1,i] 有没有合法区间划分,我们只需找到一个 \(dp_{j}=1\)\(s[i-1]-s[j]>=mid\)
然后发现可以优化直接取最小的 \(s[j]\) 即可,复杂度 \(O(n\log{n})\)

#include <bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define F first
#define S second
const int maxn=300005;
template<typename T>
void read(T &x){
	T flag=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
	for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	x*=flag;
}
int n,w[maxn],s[maxn],zz[2];
bool check(int m){
	int mn=0;//dp[0]
	for(int i=2;i<=n;i+=2)if(s[i-1]-mn>=m)mn=std::min(mn,s[i]);
	return s[n]-mn>=m;
}
int main(){
	read(n);for(int i=1;i<=n;i++)read(w[i]),zz[i&1]+=w[i];
	if(!(n&1)){
		printf("%d %d\n",std::max(zz[0],zz[1]),std::min(zz[0],zz[1]));
	}else{
		for(int i=1;i<=n;i++)s[i]=s[i-1]+(i&1?w[i]:-w[i]);
		int l=0,r=zz[0]+zz[1],res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(mid))l=mid+1,res=mid;
			else r=mid-1;
		}
		printf("%d %d\n",zz[0]+res,zz[1]-res);
	}
	return 0;
}
posted @ 2021-10-02 21:53  Semsue  阅读(16)  评论(0编辑  收藏  举报
Title