被彼此笼罩 任歌声将我们缠绕 立下誓言后再自嘲 重复仲夏夜的舞蹈 吞下这毒药

test14

占卜divination

\(c_i\) 表示查询 \([i-1,i]\) 的次数,显然如果 \(c_i\geq 4\) 那么可以确定 \(s_{i-1}/s_i\)

\(c_{l-1}=c_{r+1}=0,c_{l\to r}\neq 0\) 的一段考虑合法性。首先考虑开头的贡献,\(1\) 没用 \(2/3\) 可以确定一位,然后考虑怎么接前面的贡献,如果前面那一位可以确定那么 \(2/3\) 可以解决、\(1\) 可以确定新的位,如果前面那一位不确定看作新的开头。

#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const int N=300005;

int T, n, m, tot, x[N], cnt[N], f[N];

bool mian() {
	while(tot>0) f[tot--]=0;
	cin >> n >> m;
	up(i,1,m) cin >> x[i], ++x[i];
	sort(x+1,x+1+m);
	up(i,1,m) {
		if(i==1||x[i]!=x[i-1]) x[++tot]=x[i], cnt[tot]=1;
		else ++cnt[tot];
	} 
	up(i,1,tot) {
		if(cnt[i]>=4) return 1;
		if(f[i-1]&&x[i-1]+1==x[i]) {
			if(cnt[i]>1) return 1;
			f[i]=1;
		}
		else f[i]=(cnt[i]>1);
	}
	return 0;
}

signed main() {
//	freopen("1.txt","r",stdin);
	freopen("divination.in","r",stdin);
	freopen("divination.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> T;
	while(T--) if(mian()) cout << "Yes\n"; else cout << "No\n";
	return 0;
}

集合set

忘了怎么发现的但是就是可以发现对于 \(a\) 的合法条件是 \(\gcd\{a_i-a_{i-1}\}\neq 2^k\),这样子钦定一个端点就只有 \(\log n\)\(\gcd\) 惹,暴力做就好了。

#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const int N=200005;

int T, n, m, len, a[N], Ans, f[N], g[N], up[N];

void mian() {
	cin >> n, Ans=n, m=0;
	up(i,1,n) cin >> a[i];
	dn(i,n,2) a[i]=abs(a[i]-a[i-1]);
	up(i,2,n) while(a[i]&&a[i]%2==0) a[i]/=2;
	up(i,2,n) {
		int sav=Ans;
		up(u,1,m) g[u]=__gcd(g[u],a[i]);
		f[++m]=i, g[m]=a[i]; 
		len=0;
		up(u,1,m) if(u==1||g[u]!=g[u-1]) f[++len]=f[u], g[len]=g[u];
		m=len;
		if(g[1]==1) {
			if(m==1) Ans+=i-1;
			else Ans+=f[2]-2;
		}
		if(g[m]==0) Ans+=i-f[m]+1;
	}
	cout << Ans << '\n';
}

signed main() {
	freopen("set.in","r",stdin);
	freopen("set.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> T;
	while(T--) mian();
	return 0;
}

遗迹heritage

考虑 \(a\to b\) 的过程,对于数值 \(v\) 显然只有更小的数值有影响,且对于 \(v\) 能影响到的都希望 \(v\) 存在,那么从大到小删是不劣的。然后发现希望比要被删除的 \(v\) 小的全都在 \(v\) 的左边或者右边。

考虑一个过程,从小到大还原 \(a\),对于要删除的 \(v\),插到序列左边或者右边,对于不用删除的 \(v\),插到前缀或者后缀里面可以插的地方。那么我们可以设计一个 \(dp[i][l][r]\) 表示考虑了 \(1,\dots,i\) 左/右连着 \(l/r\) 个要删除的数的方案数,然后直接 dp 出去,要搞点组合数。

但是我们还可以学习更简洁的做法,默认不删除的原本就在,再依次加入要删除的数,这样子维护前后缀有多少个空之后可以简单转移。

#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const int N=505, P=1e9+7;

int n, m, b[N], pos[N], f[2][N][N], Ans;

inline void add(int &a,int b) { a=(a+b)%P; }

signed main() {
	freopen("heritage.in","r",stdin);
	freopen("heritage.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m, --n;
	up(i,1,m) cin >> b[i], pos[--b[i]]=i;
	if(!m) { cout << 0 << '\n'; return 0; }
	if(pos[0]) f[0][pos[0]][pos[0]+1]=1;
	else up(i,1,m+1) f[0][i][i]=1;
	up(i,1,n) {
		int now=i&1, pre=now^1;
		memset(f[now],0,sizeof(f[now]));
		if(pos[i]) {
			up(l,1,m+1) up(r,l,m+1) {
				add(f[now][min(l,pos[i])][max(r,pos[i]+1)],f[pre][l][r]);
			} 
		}
		else {
			up(r,1,m+1) {
				int sum=0;
				dn(l,r,1) {
					add(sum,f[pre][l][r]);
					add(f[now][l][r],sum);
				}
			}
			up(l,1,m+1) {
				int sum=0;
				up(r,l,m+1) {
					add(sum,f[pre][l][r]);
					add(f[now][l][r],sum);
				}
			}
		}
	}
	cout << (f[n&1][1][m+1]%P+P)%P << '\n';
	return 0;
}

博弈论game

倒序考虑 dp,设 \(f_{i,j}\) 表示考虑了 \(n,\dots,i\) 轮、有 \(j\) 个石头的胜负手。\(f_{i,}\) 是输赢交替的一段一段再拼一段平局后缀,不妨考虑维护变化的位置。然后 \(f_{i+1,}\to f_{i,}\) 的转移是隔着 \(+l/r_{i}\),打上 $\Delta $ 之后只需要去删除跨越的地方,删除的点有哪些可以用维护 rmq 的数据结构解决,删除本身可以用双向链表维护。

#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const int N=200005;

int n, m, q, tot, l[N], r[N], val[N], pre[N], nxt[N], Ans, hd, dl, dr, ans[N];
struct node {
	int i, v;
	node(int I,int V) { i=I, v=V; }
	bool operator<(const node rhs) const {
		if(v==rhs.v) return i<rhs.i;
		return v<rhs.v;
	}
}; set<node> ql, qr;

/*
1. stl 的 swap 是 O(1) 的
2. 维护区间左端点比较好(就是维护左闭右开,不用 +-1)
3. 写贡献看始终,不要随便 yy 中间贡献
4. 平局贡献是自然的 
*/ 

signed main() {
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
//	freopen("1.txt","r",stdin);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	up(i,1,n) cin >> l[i] >> r[i];
	hd=1, m=2, nxt[1]=2, pre[2]=1, val[2]=l[n];
	ql.insert(node(1,l[n]));
	dn(u,n-1,1) {
		dl+=l[u], dr+=r[u];
		// i -> j :  i+dr>j+dl , dr-dl>j-i 
		while(qr.size()&&dr-dl>=qr.begin()->v) {
			int i=qr.begin()->i, j=nxt[i];
			qr.erase(qr.begin());
			if(!nxt[j]) { nxt[i]=0; continue; }
			if(!pre[i]) hd=nxt[j];
			nxt[pre[i]]=nxt[j], pre[nxt[j]]=pre[i];
			if(pre[i]) ql.erase(node(pre[i],val[i]-val[pre[i]]));
			if(nxt[j]) ql.erase(node(j     ,val[nxt[j]]-val[j]));
			if(pre[i]&&nxt[j]) ql.insert(node(pre[i],val[nxt[j]]-val[pre[i]]));
		}
		swap(dl,dr), swap(ql,qr);
		++m, pre[hd]=m, nxt[m]=hd;
		val[m]=-dl, ql.insert(node(m,val[hd]-val[m])), hd=m;
	}
	for(int i=hd; i; i=nxt[i]) {
		ans[++tot]=val[i];
		if(tot%2==1) ans[tot]+=dl; else ans[tot]+=dr;
	}
	cin >> q;
	while(q--) {
		int x;
		cin >> x;
		int i=upper_bound(ans+1,ans+1+tot,x)-ans-1;
		if(i==tot) cout << "Draw\n";
		else {
			if(i&1) cout << "Bob\n";
			else	cout << "Alice\n";
		}
	}
	return 0;
}
posted @ 2025-10-09 22:11  Hypoxia571  阅读(6)  评论(0)    收藏  举报