敲碎不同美好记忆沉淀 搅拌直到火光熄灭 玻璃皿中颜色逐渐变浅 化作透明色的烟

test43

法阵

喜欢 cf3100 的 t1 吗,感觉这个题目推推推是笨笨的,应该直接打表观察才对!

先把题意变成大脑更容易接受的样子:横非空,纵非满,横灯连,纵空连。

我们考虑“横非空,横灯连”的形态,双射 \(n\)\(1\leq l_i\leq r_i\leq m\)

再看一下“纵非满,纵空连”带来的约束,纵着看不能有 \(1\) 前后夹着 \(0\) 但是反之可以(无歧义),所以 \(\forall i<j<k\)\([l_j,r_j]\subseteq [l_i,r_i]\cup[l_k,r_k]\),这个是充要的。

进一步的,不合法当且仅当存在 \(v\in [l_i,r_i]\),纵向 “ 存在前缀 \([l_j,r_j](j<i)\) 没有 \(v\) ” 且 " 存在后缀 \([l_j,r_j](j>i)\) 没有 \(v\) ",所以合法必须要前缀或者后缀上都有 \(v\)

试一试发现最好的切入点是“纵空连、纵非满”一定会将纵向割成前后缀,划开了上一步的 or 关系。

现在只考虑上半部分即前缀,下一行总是上一行的子区间,所以划分的那条线,只要满足先降后升即可。

现在考虑对于大前缀的那条线能拼什么样的大后缀线,首先对称的后缀线要满足先升后降。

设前缀线的底端是 \(a_{i,l\to r}\),后缀线的顶端是 \(a_{j,p\to q}\),自己手摸一下有两大情况(

  • \(i\geq j\),此时 \(r+1=p\)\(q+1=l\),因为水平对称只考虑前者。写着写着可以发现旋转一个直角变成下一种情况,只要额外要求 \(r+1\neq p\) 即可,改一下前缀和那里就好了。

  • \(i<j\),此时 \(i+1=j\)\([l,r]\)\([p,q]\) 不交,因为水平对称只考虑 \(r<p\)

    考虑枚举 \(r,p\),之后是四条路径的走法方案数量相乘,对 \(r\) 的两条路径乘积做前缀和即可。

    具体考虑方案数的时候可以看格子的边角,然后考虑哪些轮廓是固定的,然后不定轮廓的计数就是走法数。

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

using namespace std;

const int N=5005, P=998244353;

int n, m, mul[N], inv[N], sum[N], ans;

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

int coel(int x,int y) { return C(x+y,x); }

signed main() {
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	mul[0]=inv[0]=inv[1]=1;
	up(i,1,5e3) mul[i]=mul[i-1]*i%P;
	up(i,2,5e3) inv[i]=inv[P%i]*(P-P/i)%P;
	up(i,2,5e3) inv[i]=inv[i-1]*inv[i]%P;
	cin >> n >> m;
	up(i,1,n-1) {
		up(j,1,m) {
			int val=coel(i,j-1)*coel(i-1,m-j)%P;
			sum[j]=(sum[j-1]+val)%P;
		}
		up(j,1,m-1) {
			int val=sum[j]*coel(n-i-1,j)%P*coel(n-i,m-j-1)%P;
			ans=(ans+val)%P;
		}
	}
	swap(n,m);
	up(i,1,n-1) {
		up(j,1,m) {
			int val=coel(i,j-1)*coel(i-1,m-j)%P;
			sum[j]=(sum[j-1]+val)%P;
		}
		up(j,1,m-1) {
			int val=sum[j-1]*coel(n-i-1,j)%P*coel(n-i,m-j-1)%P;
			ans=(ans+val)%P;
		}
	}
	cout << 2*ans%P << '\n';
	return 0;
}

连通块

时光倒流变成加边,现在你要想想怎么找连通块里面离 \(u\) 最远的,诶离 \(u\) 最远的肯定可以作为直径的一段,进一步的 \(u\) 走到离自己最远的点肯定要经过直径中点,再进一步的考虑任意一组直径,考虑 \(u\) 到两个断电的更大值肯定就是对的了。用 \((x,y)\) 合并两个集合是简单的,考虑跨国集合的贡献,一定走到 \(x,y\) 的,那就分别考虑子集里面里 \(x/y\) 最远的,显然可以就拿你那原本的端点来更新。

#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 pb push_back

using namespace std;

const int N=200005, M=21;

int n, m, T, u[N], v[N], dep[N], fa[N][M], l[N], r[N], dsu[N];
int opt[N], id[N], ans[N], del[N];
vector<int> to[N];

void dfs(int x,int fad) {
	dep[x]=dep[fad]+1, fa[x][0]=fad;
	up(i,1,T) fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int y:to[x]) if(y!=fad) dfs(y,x);
}

int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	dn(i,T,0) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	if(x==y) return x;
	dn(i,T,0) if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i];
	return fa[x][0];
}

int dis(int x,int y) {
	return dep[x]+dep[y]-2*dep[lca(x,y)];
}

int get(int x) { return x==dsu[x]?x:dsu[x]=get(dsu[x]); }

int query(int x) {
	int p=get(x);
	return max(dis(x,l[p]),dis(x,r[p]));
}

void insert(int x,int y) {
	x=get(x), y=get(y), dsu[x]=y;
	int a=l[x], b=r[x], c=l[y], d=r[y];
	if(dis(a,b)>dis(l[y],r[y])) l[y]=a, r[y]=b;
	if(dis(a,c)>dis(l[y],r[y])) l[y]=a, r[y]=c;
	if(dis(a,d)>dis(l[y],r[y])) l[y]=a, r[y]=d;
	if(dis(b,c)>dis(l[y],r[y])) l[y]=b, r[y]=c;
	if(dis(b,d)>dis(l[y],r[y])) l[y]=b, r[y]=d;
}

signed main() {
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m, T=__lg(n);
	up(i,1,n-1) {
		cin >> u[i] >> v[i];
		to[u[i]].pb(v[i]);
		to[v[i]].pb(u[i]);
	}
	up(i,1,m) {
		cin >> opt[i] >> id[i];
		if(opt[i]==1) del[id[i]]=1;
	}
	dfs(1,0);
	up(i,1,n) dsu[i]=l[i]=r[i]=i;
	up(i,1,n-1) if(!del[i]) insert(u[i],v[i]);
	dn(i,m,1) {
		if(opt[i]==1) { insert(u[id[i]],v[id[i]]); }
		else ans[i]=query(id[i])+1;
	}
	up(i,1,m) if(ans[i]) cout << ans[i]-1 << '\n';
	return 0;
}

军队

显然只关心每一行的 \(mi_i=\min\{男的,男娘\}\) ,小学奥数和一定差小积大,所以询问是对 \(mi\) 排序然后二分,就是你优先考虑 \(\lfloor\frac{y}{2}\rfloor\lceil\frac{y}{2}\rceil\),不然就让 \(mi\) 尽量小。

经典有脑扫描线,区间加减,查询区间 \(<k/\geq k\) 的数量,后面那个不好做做前面那个,考虑什么可以 pushup,发现可以维护区间最小的 \(10\) 个权值 ,顺便计数,然后就做完了。

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

using namespace std;

const int N=300005, M=11;

int n, m, c, tot, k, q, mi[N], tag[N<<2], lsy[N], lkx[N];
struct Seg { int pos, l, r, v; } arr[N<<1];
struct node { int len; pii u[M]; } tr[N<<2];

inline void tup(int p) {
	int &len=(tr[p].len=0), i=1, j=1;
	while(len<10&&i<=tr[ls(p)].len&&j<=tr[rs(p)].len) {
		int l=tr[ls(p)].u[i].fir, r=tr[rs(p)].u[j].fir;
		if(l==r) {
			tr[p].u[++len]=mp(l,tr[ls(p)].u[i++].sec+tr[rs(p)].u[j++].sec);
			continue;
		}
		if(l<r) tr[p].u[++len]=tr[ls(p)].u[i++];
		else	tr[p].u[++len]=tr[rs(p)].u[j++];
	}
	while(len<10&&i<=tr[ls(p)].len) tr[p].u[++len]=tr[ls(p)].u[i++];
	while(len<10&&j<=tr[rs(p)].len) tr[p].u[++len]=tr[rs(p)].u[j++];
} 

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

inline void add(node &p,int v) {
	up(i,1,p.len) p.u[i].fir+=v;
}

void tdn(int p) {
	if(!tag[p]) return;
	tag[ls(p)]+=tag[p], add(tr[ls(p)],tag[p]);
	tag[rs(p)]+=tag[p], add(tr[rs(p)],tag[p]);
	tag[p]=0;
}

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

signed main() {
	freopen("army.in","r",stdin);
	freopen("army.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> c >> k >> q;
	while(c--) {
		int x, y, xx, yy;
		cin >> x >> y >> xx >> yy;
		arr[++tot]=(Seg){x,y,yy,1};
		arr[++tot]=(Seg){xx+1,y,yy,-1};
	}
	sort(arr+1,arr+1+tot,[](Seg i,Seg j){return i.pos<j.pos;});
	build();
	int j=1; 
	up(i,1,n) {
		while(j<=tot&&arr[j].pos==i) {
			int l=arr[j].l, r=arr[j].r, v=arr[j].v;
			++j, modify(l,r,v); 
		} 
		up(j,1,tr[1].len) if(tr[1].u[j].fir<k) mi[i]+=tr[1].u[j].sec;
		mi[i]=min(mi[i],m-mi[i]);
	}
	sort(mi+1,mi+1+n,[](int i,int j){return i>j;});
	up(i,1,n) lsy[i]=lsy[i-1]+mi[i], lkx[i]=lkx[i-1]+mi[i]*mi[i];
	while(q--) {
		int cnt, res, l=1, r=n, p=0;
		cin >> cnt >> res;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(mi[mid]>=res/2) p=mid, l=mid+1;
			else r=mid-1;
		}
		if(p>=cnt) cout << (res/2)*((res+1)/2)*cnt << '\n';
		else {
			int ans=(res/2)*((res+1)/2)*p;
			ans+=(lsy[cnt]-lsy[p])*res-(lkx[cnt]-lkx[p]);
			cout << ans << '\n'; 
		}
	}
	return 0;
}
posted @ 2025-11-19 22:16  Hypoxia571  阅读(11)  评论(0)    收藏  举报