如果一切都能在搞砸之前排练 如果能够忘记所有不计前嫌 怎样找回已消失许久那条红线 心被分成一片一片

test45

今天怎么第一版 t1/t2 都是假的(


构造字符串

数据范围不是特别大直接改条件 \(a_i=a_j/a_i\neq a_j\),你并查集前一种之后看一下符不符合第二种,如果符合的话肯定有解,比方说你考虑每个连通块给不同颜色。

注意到你目前给一个子集上合法颜色不会对未来造成影响,最小化字典序就是你想咋样就咋样咯,所以按照连通块中最小的标号这个顺序做简单贪心。

#pragma GCC optimize(1,2,3,"Ofast","inline")
#include<bits/stdc++.h>
#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 pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fir first
#define sec second

using namespace std;

const int N=1005;

int n, m, dsu[N], ans[N];
bool chk[N][N];
vector<int> to[N];

int get(int x) { return x==dsu[x]?x:dsu[x]=get(dsu[x]); }
void merge(int x,int y) { x=get(x), y=get(y), dsu[x]=y; }

signed main() {
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	up(i,1,n) dsu[i]=i;
	while(m--) {
		int i, j, z;
		cin >> i >> j >> z;
		up(u,0,z-1) merge(i+u,j+u);
		if(i+z<=n&&j+z<=n) {
			to[i+z].pb(j+z);
			to[j+z].pb(i+z);
		}
	}
	up(i,1,n) for(int j:to[i]) if(get(i)==get(j)) { cout << -1 << '\n'; return 0; }
	up(i,1,n) if(!ans[i]) {
		int u=get(i), in=0;
		up(j,1,n) if(!chk[u][j]) { in=j; break; }
		up(j,1,n) if(get(j)==u) {
			ans[j]=in;
			for(int k:to[j]) chk[get(k)][in]=1;
		}
	}
	up(i,1,n) cout << ans[i]-1 << ' ';
	return 0;
}

寻宝

咋又是这种题目,好无趣。原图上联通的缩成点,然后用有向图的强连通再缩一次点。之后要做的是 DAG 图连通性,这个问题最多只能 bitset 处理,但是这个题目 \(k\)\(100\) 诶那随便搞了。

#pragma GCC optimize(1,2,3,"Ofast","inline")
#include<bits/stdc++.h>
#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 pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second 
#define pb push_back

using namespace std;

const int N=50005, M=21, K=305;
const int dx[4]={1,-1,0,0}, dy[4]={0,0,1,-1};

int n, m, tot, k, T, gp[N], vis[N], stk[N], in[N], top, cnt;
vector<int> id[N], F[N], G[N], to[N];
vector<char> str[N];
bitset<N> a[N/2];

void dfs1(int x) {
	for(int y:F[x]) if(!vis[y]) vis[y]=1, dfs1(y);
	stk[++top]=x;
}

void dfs2(int x) {
	for(int y:G[x]) if(!gp[y]) gp[y]=cnt, dfs2(y); 
}

signed main() {
	freopen("treasure.in","r",stdin);
	freopen("treasure.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> k >> T;
	up(i,0,n+1) id[i].resize(m+2), str[i].resize(m+2);
	up(i,1,n) cin >> &str[i][1];
	up(i,1,n) up(j,1,m) if(str[i][j]=='.'&&!id[i][j]) {
		queue<pii> q; 
		id[i][j]=++tot, q.push(mp(i,j));
		while(q.size()) {
			int x=q.front().fir, y=q.front().sec;
			q.pop();
			up(o,0,3) {
				int xx=x+dx[o], yy=y+dy[o];
				if(str[xx][yy]=='.'&&!id[xx][yy]) {
					id[xx][yy]=tot;
					q.push(mp(xx,yy));
				}
			}
		}
	}
	while(k--) {
		int x, y, l, r;
		cin >> x >> y, l=id[x][y];
		cin >> x >> y, r=id[x][y];
		if(l!=r) F[l].pb(r), G[r].pb(l);
	}
	up(i,1,tot) if(!vis[i]) vis[i]=1, dfs1(i);
	dn(u,tot,1) {
		int i=stk[u];
		if(!gp[i]) gp[i]=++cnt, dfs2(i);
	} 
	up(i,1,tot) for(int j:F[i]) {
		if(gp[i]==gp[j]) continue;
		to[gp[j]].pb(gp[i]), ++in[gp[i]]; 
	}
	queue<int> q;
	up(i,1,cnt) if(!in[i]) q.push(i);
	while(q.size()) {
		int x=q.front(); q.pop();
		a[x][x]=1;
		for(int y:to[x]) {
			a[y]|=a[x];
			if(!--in[y]) q.push(y);
		}
	}
	while(T--) {
		int x, y, l, r;
		cin >> x >> y, l=id[x][y], l=gp[l];
		cin >> x >> y, r=id[x][y], r=gp[r];
		cout << a[l][r] << '\n';
	}
	return 0;
}

序列

转成前缀和,变成了找前后缀在 \(x=k\) 的 rmq,离线扫 \(p\uparrow\) 就是加线段查询李超线段树随便写写得了。

#pragma GCC optimize(1,2,3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
#define ll __int128
#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 pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)

using namespace std;

const int N=1000005, inf=1e18;

int n, T, sp[N], m, a[N], b[N], tag[N<<2], ans[N];
pii tr[N<<2];
struct QUR { int p, k, id; } q[N];

int calc(pii x,int u) { return max((ll)x.fir*u+x.sec,(ll)-inf); }
bool check(pii l,pii r,int u) { return calc(l,u)>=calc(r,u); }

void build(int p=1,int s=1,int e=m) {
	tr[p]=mp(0,0), tag[p]=1;
	if(s==e) return;
	int mid=(s+e)>>1;
	build(ls(p),s,mid), build(rs(p),mid+1,e);
}

void modify(pii u,int p=1,int s=1,int e=m) {
	int mid=(s+e)>>1;
	if(tag[p]) return tr[p]=u, tag[p]=0, void();
	if(check(u,tr[p],sp[mid])) swap(u,tr[p]);
	if(check(tr[p],u,sp[s])&&check(tr[p],u,sp[e])) return;
	if(check(u,tr[p],sp[s])) modify(u,ls(p),s,mid);
	if(check(u,tr[p],sp[e])) modify(u,rs(p),mid+1,e);
}

int query(int v,int p=1,int s=1,int e=m) {
	int res=tag[p]?-inf:calc(tr[p],v);
	if(s==e) return res;
	int mid=(s+e)>>1;
	if(v<=sp[mid]) res=max(res,query(v,ls(p),s,mid));
	if(v>sp[mid]) res=max(res,query(v,rs(p),mid+1,e));
	return res;
}

signed main() {
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> T;
	up(i,1,n) {
		cin >> a[i] >> b[i];
		a[i]+=a[i-1], b[i]+=b[i-1];
	}
	up(i,1,T) {
		int p, k;
		cin >> p >> k;
		q[i]=(QUR){p,k,i};
		sp[++m]=k;
	}
	sort(sp+1,sp+1+m), m=unique(sp+1,sp+1+m)-sp-1;
	sort(q+1,q+1+T,[](QUR i,QUR j){return i.p<j.p;});
	
	int j=1;
	build(); modify(mp(0,0));
	up(i,1,n) {
		while(j<=T&&q[j].p==i) {
			ans[q[j].id]+=query(q[j].k);
			++j;
		}
		modify(mp(b[i],-a[i]));
	}
	j=T;
	build();
	dn(i,n,1) {
		modify(mp(-b[i],a[i]));
		while(j>=1&&q[j].p==i) {
			ans[q[j].id]+=query(q[j].k);
			--j;
		}
	}
	up(i,1,T) cout << ans[i] << '\n';
	return 0;
}

构树

考虑断掉 \(k\) 条边重构的方案数是什么,关心连通块大小 \(p_1,\dots,p_{k+1}\),想做这个这个还有一个重构到合法边的问题,设 \(g_i\) 表示拆掉 \(i\) 条边重构的方案数,\(f_i\) 表示有 \(i\) 条边不同的方案数,显然是 \(g_i=\sum_{j=0}^i f_j\binom{n-1-j}{i-j}\),可以二项式反演到 \(f_i=\sum_{j=0}^i(-1)^{j-i}\binom{n-1-j}{i-j}g_j\),之后就彻底只关心 \(\{p\}\) 惹。

早上对着这玩意弄了半个早上,其实有尝试 Prufer 了但是无法成功,谁懂看到是 神秘公式 的救赎感...?\(n\) 个点形成 \(m\) 个连通块用 \(m-1\) 条边将其联通的方案数是 \(n^{m-2}\prod siz_u\)。前面那个显然对于 \(k\) 是常数,现在考虑怎么算对于 \(k\)\(\prod siz_u\),初始想法可以 \(f[u][i][v]\) 表示子树 \(u\)\(i\) 条断边当前连通块有 \(v\) 个点的方案数。考虑怎么扔掉一维,还是比较经典的你可以去组合意义 \(\prod siz_u\),每个连通块选一个点的方案数,你直接上 \(f_{u,0/1}\) 表示子树根所在连通块有没有选点的方案数,好转移,你这里带着钦定去转移就好了,还是好写的。

最后就是空间开不下,按照树形背包优化空间的办法可以降掉一个 \(n\),具体而言 \(u\) 只需要 \(siz_u\) 的空间,只要你能保证祖先链上没有太多同时存在就够了,那很自然的会去想把 dp 数组存在 dfn 的区间上面。

所以这算是经典 trick 拼好题吗?

#pragma GCC optimize(1,2,3,"Ofast","inline")
#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 pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fir first
#define sec second

using namespace std;

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

int n, siz[N], f[N][2], g[N][2], dfn[N], stp;
int pw[N], mul[N], inv[N], F[N], G[N];
vector<int> to[N];

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

void dfs(int x,int fad) {
	dfn[x]=++stp, siz[x]=f[dfn[x]][0]=f[dfn[x]][1]=1;
	for(int y:to[x]) if(y!=fad) {
		dfs(y,x);
		up(i,0,siz[x]+siz[y]-1) g[i][0]=g[i][1]=0; 
		up(l,0,siz[x]-1) up(r,0,siz[y]-1) {
			add(g[l+r][0],f[dfn[x]+l][0]*f[dfn[y]+r][0]%P);
			add(g[l+r+1][0],f[dfn[x]+l][0]*f[dfn[y]+r][1]%P);
			add(g[l+r][1],f[dfn[x]+l][1]*f[dfn[y]+r][0]%P);
			add(g[l+r+1][1],f[dfn[x]+l][1]*f[dfn[y]+r][1]%P);
			add(g[l+r][1],f[dfn[x]+l][0]*f[dfn[y]+r][1]%P);
		}
		up(i,0,siz[x]+siz[y]-1) f[dfn[x]+i][0]=g[i][0], f[dfn[x]+i][1]=g[i][1];
		siz[x]+=siz[y];
	}
}

int C(int n,int m) {
	if(n<m||m<0) return 0;
	return mul[n]*inv[m]%P*inv[n-m]%P;
}

signed main() {
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n, pw[0]=mul[0]=inv[0]=inv[1]=1;
	up(i,1,n) pw[i]=pw[i-1]*n%P;
	up(i,1,n) mul[i]=mul[i-1]*i%P;
	up(i,2,n) inv[i]=inv[P%i]*(P-P/i)%P;
	up(i,2,n) inv[i]=inv[i-1]*inv[i]%P;
	up(i,2,n) {
		int u, v;
		cin >> u >> v;
		to[u].pb(v);
		to[v].pb(u);
	}
	dfs(1,0);
	up(i,0,n-1) G[i]=(i==0)?1:(pw[i-1]*f[1+i][1]%P);
	dn(i,n-1,0) {
		up(j,0,i) {
			int val=G[j]*C(n-1-j,i-j)%P; 
			if((i-j)%2==0) add(F[i],+val);
			if((i-j)%2==1) add(F[i],-val);	
		}
		cout << (F[i]%P+P)%P << ' ';
	}
	return 0;
}
posted @ 2025-11-21 19:45  Hypoxia571  阅读(12)  评论(0)    收藏  举报