理解否 我尝试和你握手 心平气和地交流 别害怕曾经出丑 我不走

t4 是 P11945,前面三题简单爬不到懒得找了

test3

答答题sol

本可看错题了搞了半天我不行了。

完全图 \(i\to j\) 的边权是 \(|2^i-2^j|\),贡献是 \(|s_i-s_j|\),要求边权单调递增的路径的最大贡献和。

要求边权单调递增又边权严格不相等,那么边两两之间的前后顺序一定,设 \(dis_u\) 表示以 \(u\) 结尾的最长路,可以考虑按照边权从小到大去取边更新。问题在于因为空间限制边存不下来,氮素考虑到 \(2^i\) 量级差很大直接先枚举 \(i=1\to n\) 再枚举 \(j=i-1\to 1\) 就单调惹,严谨地说 \(2^i>2^i-2^j\geq 2^{i-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=5005, inf=1e13;

int T, n, s[N], tag[N], f[N], Ans;

void mian() {
	cin >> n, Ans=-inf;
	up(i,1,n) f[i]=0;
	up(i,1,n) cin >> tag[i];
	up(i,1,n) cin >> s[i];
	up(i,1,n) dn(j,i-1,1) if(tag[i]!=tag[j]) {
		int fi=f[i], fj=f[j];
		f[i]=max(f[i],fj+abs(s[i]-s[j]));
		f[j]=max(f[j],fi+abs(s[i]-s[j]));
	}
	up(i,1,n) Ans=max(Ans,f[i]);
	cout << Ans << '\n';
}

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

金币coin

嗯大概就是 \(1+ki\) 看起来很不顺眼吧不妨令编号变成 \(0,\dots,n-1\),这样子每一轮去掉 \(k\) 的倍数位置上的人,所以一轮的位置变化形如 \(x\to x-\lfloor \frac{x}{k}\rfloor-1\)。然后有一个观察就是只有 \(ik\)\(ik-1\) 这样变化之后的值相同。

正着推很难想想怎么倒过来,对于 \(y \to x\)\(y-\lfloor \frac{y}{k}\rfloor-1=x\),考虑到相同的值只有两个直接乘出来取小的就可以惹,嗯对就是 \(\lfloor\frac{xk}{k-1}\rfloor+1\to x\),注意到暴力可以做轮数不多的就是 \(k\leq \sqrt m\) 的了,后面只用考虑怎么做 \(k>\sqrt m\)

\(k>\sqrt m\) 的话显然 \(\lfloor\frac{x}{k}\rfloor\leq \sqrt m\) (其实带等号是xp),不妨考虑将增量相等的放在一起做,从小到大考虑增量 \(i\) 要加 \(Ans\) 次,只需要考虑一边的不等式 \(\lfloor\frac{x+Ans\times i}{k}\rfloor+1=i\),至少有一个 \(Ans\leq \frac{(i-1)k-x}{i}\),这个时候我们会困惑要是算出来 \(k|x+Ans\times i\) 怎么办惹,氮素注意到在两个位置都能被表示的前提下尽量多加 \(ik-1\) 肯定比 \(ik\) 优先就不困惑惹。

最后就是 lgj oj 对我来讲有点卡常,可以调 \((k=2)\sqrt m\) 为界限,再加上火车头。

#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;

int T, n, m, k;

void mian() {
	cin >> n >> k, m=sqrt(n)/2;
	if(k<=m) {
		int now=0, cnt=0;
		__int128 p;
		while((p=(__int128)now*k/(k-1)+1)<n) {
			if(++cnt>1e7) {
				assert(0);
			}
			now=p;
		}
		cout << now+1 << '\n';
	}
	if(k>m) {
		int now=0;
		up(i,1,n/m) {
			int l=(n-now+i-1)/i-1, r=(i*k-now+i-1)/i-1;
			if(l<r) now+=i*l; else now+=i*r;
		}
		cout << now+1 << '\n';
	}
}

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

围棋go

设选手 \(i\) 的能力值分别为 \(rk_{i,1/2/3}\),现在考虑选手 \(u\) 是否能成为冠军。

对于选手 \(i,j\),如果分别有 \(rk_{i,1/2/3}<rk_{j,1/2/3}\),那么 \(j\) 不能直接打败 \(i\),反之亦然。然后 \(i,j\) 的关系除了偏序还可以是能互相打败,对此关系连边,有一个观察,在一个联通块中谁都能成为这个连通块的冠军,具体而言可以随便拉一棵生成树,以目标冠军为根从下往上打败。容易发现在计算出了连通块之后,不同连通块之间肯定是都三维偏序的否则会有 \(rk\) 的交叉,现在只用考虑 \(u\) 是否在 \(rk\) 最大的那个连通块中间。

现在问题变成对题目中的三个数组 \(a/b/c\) 查找一个最小的前缀 \(p\),满足 \(\{a/b/c_1,\dots,a/b/c_p\}\) 素相同的集合,也就是要求满足 \(rk_{i,1/2/3}\leq p\)\(i\)\(p\) 个惹,那么对于每一个 \(i\) 可以贡献给位置 \(\max\{rk_{i,1/2/3}\},\dots,n\) 对应的前缀咯,用线段树维护 \(sum_p-p\) 的最大值然后线段树上二分即可求出 \(p\)

#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)
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)

using namespace std;

const int N=100005;

int n, q, rd, a[N][4], tr[N<<2], tag[N<<2];

inline void tup(int p) {
	tr[p]=max(tr[ls(p)],tr[rs(p)]);
}

inline void tdn(int p) {
	tag[ls(p)]+=tag[p], tr[ls(p)]+=tag[p];
	tag[rs(p)]+=tag[p], tr[rs(p)]+=tag[p];
	tag[p]=0;
}

void build(int p=1,int s=1,int e=n) {
	if(s==e) {
		tr[p]=-s;
		return;
	}
	int mid=(s+e)>>1;
	build(ls(p),s,mid);
	build(rs(p),mid+1,e);
	tup(p); 
}

void upt(int l,int r,int v,int p=1,int s=1,int e=n) {
	if(l<=s&&e<=r) {
		tag[p]+=v, tr[p]+=v;
		return;
	}
	tdn(p);
	int mid=(s+e)>>1;
	if(l<=mid) upt(l,r,v,ls(p),s,mid);
	if(r>mid) upt(l,r,v,rs(p),mid+1,e);
	tup(p);
}

int query(int p=1,int s=1,int e=n) {
	if(s==e) return s;
	tdn(p);
	int mid=(s+e)>>1;
	if(tr[ls(p)]>=0) return query(ls(p),s,mid);
	return query(rs(p),mid+1,e); 
}

inline int Max(int i) {
	return max(max(a[i][1],a[i][2]),a[i][3]);
}

void add(int i) {
	upt(Max(i),n,1);
}

void del(int i) {
	upt(Max(i),n,-1);
}

signed main() {
	freopen("go.in","r",stdin);
	freopen("go.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> q, build();
	up(j,1,3) up(i,1,n) cin >> rd, a[rd][j]=i;
	up(i,1,n) add(i);
	while(q--) {
		int opt, x, y, p;
		cin >> opt;
		if(opt==1) {
			cin >> x;
			if(Max(x)<=query()) cout << "DA\n";
			else cout << "NE\n";
		}
		if(opt==2) {
			cin >> p >> x >> y;
			del(x), del(y);
			swap(a[x][p],a[y][p]);
			add(x), add(y);
		}
	}
	return 0;
}

图好数tu

本可要晕惹。

先给当前点 \(u\) 分类,用 \(x\to y\) 表示 \(x\) 可以走到 \(y\),这里 \(x=y\) 素允许的,数字表示某个类型的点,\(s/t\) 表示起点终点。

对于每一类点给出必须/可以连的边有什么。

  • 1 类点:\(s\) 能到,能到 \(t\)

    必须要有 \(1\to u\to 1\),可以有 \(u\to 2,3\to u\)

  • 2 类点:只有 \(s\) 能到

    必须要有 \(1/2\to u\),可以有 \(u\to 2,3\to u\)

  • 3 类点:只有能到 \(t\) 或者 什么都没有 /wq

    可以有 \(u\to 1,3\to u,u\to 3,u\to 2\)

边类型有 \(1\to1 ,1\to 2,3\to 1,2\to 2,3\to 2,3\to 3\),考虑一下每条边的贡献分到前还是后面的点考虑,以及那么看看哪些可以独立出来算降低难度。

  • \(1\to 1\) 可以独立出来,反正 1 类点全都联通。要求大小为 \(k=1,\dots,n\) 的每个点都有 \(s\to u\to t\) 的图的数量。(独立出来只需要知道 \(k\) !)

    计数肯定是希望找到简单的充要条件的,首先容易想到保证 \(i(\neq t)\) 都有出度可以保证 \(i\to t\),氮素有些点根本从 \(s\) 走不到,考虑强制 \(i(\neq s)\) 有入度,这样子所有的点都有 能从 \(s\) 到且能去 \(t\) 惹,因为前后肯定都可以一直走。想要 \(i(\neq t)\) 有出度只要 dp 的时候保证有向后的连边就可以惹,设 \(dp[i][j]\) 表示有 \(i\) 个点入度为 \(0\) 的点有 \(j\) 个,发现转移要枚举 \(dp[i-1][k]\) 且分别要乘二元的组合数复杂度被卡在 \(O(n^3)\)。考虑二项式反演,把恰好转成钦定,设 \(g[i][j]\) 表示 \(i\) 个点钦定 \(j\) 个点入度为 \(0\)(包括 \(s\))的方案数,\(i\) 个点的答案 \(D[i]=\sum_{j=1}^i (-1)^{j-1}\binom{i}{j} dp[i][j]\),然后递推素考虑一下以前的起点要不要钦定有 \(dp[i][j]=(2^{i-j}-1)dp[i-1][j-1]+(2^{i-j}-1)dp[i-1][j]\),嗯对。

  • 因为对于 2 类点 \(u\)\(1/2\to 2\) 是必要的,所以 \(1\to 2,2\to 2\) 被迫一起考虑惹。然后剩下 \(3\to 1/2/3\),都在 \(3\) 那里做贡献设 \(3\) 的位置集合为 \(\{p\}\),这些贡献是 \(\prod_{p_i}2^{n-p_i}\),又因为 \(1/2\to 2\) 关系的不是具体位置而是前面某类点有几个,\(3\to 1/2/3\) 的贡献也能被本可独立出来了,具体而言假设有 \(c=|p|\)\(3\),去 dp 贡献 \(\sum_{|p|=c}\prod_{p_i}2^{n-p_i}\) 乘起来即可,\(g[n][c]\) 求起来是简单 dp 设 \(g[i][j]\) 表示考虑了 \(1,\dots,i\) 选了 \(j\) 个的贡献,转移是背包考虑选不选然后乘上贡献,就素 \(s,t\) 不能选。那么只剩下考虑 \(1/2\to 2\) 的方案数惹,也素直接背包就行了,设计 \(f[i][j]\) 表示考虑了 \(i\) 个有 \(j\) 个 1 类 \(i-j\) 个 2 类这种,还素 \(s,t\) 是 1。

最后合并一下贡献,枚举有 \(i\) 个一类点,\(j\) 个二类点,\(n-i-j\) 个三类点,\(D_i\times g[n][n-i-j]\times f[i+j][i]\) 贡献到 $ Ans_i$。

嗯还有就是 \(Ans_0=Ans_2\),就素有没有 \(1\to n\) 的双射罢惹。

#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=2005; 

int n, P, dp[N][N], D[N], f[N][N], g[N][N], pw[N], yh[N][N], Ans[N];

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

inline int C(int x,int y) {
	if(x<y||y<0) return 0;
	return yh[x][y];
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> P;
	pw[0]=1;
	up(i,1,n) pw[i]=2*pw[i-1]%P;
	up(i,0,n) {
		yh[i][0]=yh[i][i]=1;
		up(j,1,i-1) yh[i][j]=(yh[i-1][j-1]+yh[i][j])%P;
	}
	dp[1][1]=1;
	up(i,2,n) up(j,1,i) {
		add(dp[i][j],(pw[i-j]-1)%P*dp[i-1][j-1]%P);
		add(dp[i][j],(pw[i-j]-1)%P*dp[i-1][j]%P);
	}
	up(i,1,n) up(j,1,i) add(D[i],(j%2==1?1:-1)*C(i,j)%P*dp[i][j]%P);
	f[1][1]=1;
	up(i,2,n) up(j,1,i) {
		add(f[i][j],f[i-1][j-1]);
		add(f[i][j],(pw[i-1]-1)%P*f[i-1][j]%P);
	}
	dn(i,n,2) up(j,1,i) f[i][j]=f[i-1][j-1];
	g[1][0]=1;
	up(i,2,n) up(j,0,i) {
		add(g[i][j],g[i-1][j]);
		if(j) add(g[i][j],pw[n-i]*g[i-1][j-1]%P);
	}
	dn(i,n,2) up(j,1,i) g[i][j]=g[i-1][j];
	up(i,1,n) up(j,0,n-i) add(Ans[i],D[i]*f[i+j][i]%P*g[n][n-i-j]%P);
	Ans[0]=Ans[2];
	up(i,0,n) cout << (Ans[i]%P+P)%P << ' ';
	return 0;
}
posted @ 2025-09-11 19:03  Hypoxia571  阅读(18)  评论(0)    收藏  举报