2020080模拟赛【横纵坐标互不影响,仙人掌最小割,组合意义翻译式子】

T1 这把我们就遇到了高手了

题目描述

X ∗ Y X*Y XY 的环形网格(左边和右边连通,上边和下边连通)。
给出 n n n 个矩形的左下和右上点对,每个矩形可以选择四种方式之一摆放:
在这里插入图片描述
n n n 个矩形能同时覆盖的最大面积。
在这里插入图片描述

样例:
在这里插入图片描述

题目分析

乍一看非常不可做。
仔细思考/打完暴力 感觉这个矩形的形式非常的美妙。恰好是4种方式,横纵坐标各2种。

这启示我们可以把横纵坐标分开看,然后把两个的最优答案乘起来

然后问题就变成了一维的,每个矩形变成一条线段,把数轴划分成很多段。
那么每段有一个状态,对应每个矩形的线段取的是里面还是外面。
状态相同的就可以同时取到。

那么给这个 01 串设个哈希值,哈希值相同的段就加起来,取最大长度即可。
从左往右扫描即可,可以用map,更快的做法是得到每段哈希值后再按哈希值排序。

Code:

#include<bits/stdc++.h>
#define maxn 500005
#define y1 y_1
#define LL long long
#define ULL unsigned long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,X,Y,x1[maxn],y1[maxn],x2[maxn],y2[maxn];
ULL pw[maxn];
struct node{
	int x; ULL v;
	bool operator < (const node &p)const{return x<p.x;}
}q[maxn*2]; int cnt;
map<ULL,int>val;
int solve(){
	ULL H=0; val.clear();
	int ret=0;
	sort(q+1,q+1+cnt);
	rep(i,0,cnt-1){
		H+=q[i].v;
		ret=max(ret,val[H]+=q[i+1].x-q[i].x);
	}
	return ret;
}
int main()
{
	freopen("master.in","r",stdin);
	freopen("master.out","w",stdout);
	read(n),read(X),read(Y);
	rep(i,1,n) read(x1[i]),read(y1[i]),read(x2[i]),read(y2[i]);
	rep(i,pw[0]=1,n) pw[i]=pw[i-1]*137;
	q[cnt=1]=(node){X,0};
	rep(i,1,n) q[++cnt]=(node){x1[i],pw[i]},q[++cnt]=(node){x2[i],-pw[i]};
	int ansx = solve();
	q[cnt=1]=(node){Y,0};
	rep(i,1,n) q[++cnt]=(node){y1[i],pw[i]},q[++cnt]=(node){y2[i],-pw[i]};
	int ansy = solve();
	printf("%lld\n",1ll*ansx*ansy);
}

T2 ok起飞

题目描述

一棵仙人掌,有边权,设 f ( s , t ) f(s,t) f(s,t) s → t s\to t st 的最小割,求 ∑ 1 ≤ s < t ≤ n f ( s , t ) ⊕ s ⊕ t \sum_{1\le s<t\le n}f(s,t)\oplus s\oplus t 1s<tnf(s,t)st

n ≤ 1 0 5 , w i ≤ 1 0 6 n\le 10^5,w_i\le 10^6 n105,wi106

题目分析

先考虑树的情况,显然是先取最小的边把两边分开,然后递归,但是这样不好实现,可以从大到小加入边然后用并查集合并,由于编号要取异或,把二进制位分开统计,记录连通块内第 k k k 位为 0/1 的点的个数,然后根据边权第 k k k 位的情况统计权值。

仙人掌的话,如果最小割落在环上,那么必然会割掉两条边,并且环上的最小边一定会被割,那么把最小边删掉,把它的权值加到环上的其它边上,最小割不会改变。
于是就变成了树的情况。

复杂度 O ( n log ⁡ V ) O(n\log V) O(nlogV)

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 400005
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int T,n,m;
int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot=1;
void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
int dfn[maxn],low[maxn],tim,stk[maxn*2],top;
struct edge{
	int x,y,w;
	bool operator < (const edge &p)const{return w>p.w;}
}e[maxn]; int cnt;
void tarjan(int u,int ff){
	dfn[u]=low[u]=++tim;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
		if(!dfn[v]){
			stk[++top]=i;
			tarjan(v,u),low[u]=min(low[u],low[v]);
			if(dfn[u]<low[v]) e[++cnt]=(edge){u,v,w[i]},top--;
			else if(dfn[u]==low[v]){
				int mn=1e9,id;
				for(int t=-1,j=top;t!=i;) t=stk[j--],mn>w[t]&&(mn=w[t],id=t);
				for(int t=-1;t!=i;) t=stk[top--],id!=t&&(e[++cnt]=(edge){to[t^1],to[t],w[t]+mn},0); 
			}
		}
		else if(dfn[v]<dfn[u]) stk[++top]=i,low[u]=min(low[u],dfn[v]);
	}
}
int f[maxn],s[maxn][21][2];
int find(int x){return !f[x]?x:f[x]=find(f[x]);}
int main()
{
	freopen("okfly.in","r",stdin);
	freopen("okfly.out","w",stdout);
	read(T);
	while(T--){
		read(n),read(m);
		memset(fir,0,(n+1)<<2),tot=1;
		memset(dfn,0,(n+1)<<2),tim=top=0;
		cnt=0;
		for(int i=1,x,y,z;i<=m;i++) read(x),read(y),read(z),line(x,y,z),line(y,x,z);
		tarjan(1,0);
		long long ans=0;
		sort(e+1,e+1+cnt);
		//for(int i=1;i<=cnt;i++) cout<<e[i].x<<' '<<e[i].y<<' '<<e[i].w<<endl;
		for(int i=1;i<=n;i++){
			f[i]=0;
			for(int j=0;j<=20;j++) s[i][j][0]=s[i][j][1]=0,s[i][j][i>>j&1]++;
		}
		for(int i=1;i<=cnt;i++){
			int x=find(e[i].x),y=find(e[i].y);
			for(int k=0;k<=20;k++){
				int w=e[i].w>>k&1;
				for(int l=0;l<2;l++) ans+=1ll*s[x][k][l]*s[y][k][l^w^1]*(1<<k);
			}
			f[y]=x;
			for(int k=0;k<=20;k++) s[x][k][0]+=s[y][k][0],s[x][k][1]+=s[y][k][1];
		}
		printf("%lld\n",ans);
	}
}

T3 这钵和餐厅配合的不是很好

题目描述

长度为 n n n 的序列 a i a_i ai n ≤ 200 , 1 ≤ a i ≤ 1000 n\le 200,1\le a_i\le 1000 n200,1ai1000

对于一个排列 p p p,令 b i = a p i , s i = ∑ j = 1 i b j b_i=a_{p_i}, s_i=\sum_{j=1}^ib_j bi=api,si=j=1ibj,贡献为 1 ∏ i = 2 n s i \frac 1{\prod_{i=2}^ns_i} i=2nsi1

求所有排列的贡献之和。

题目分析

这篇 blog 讲得蛮好。

介绍一下硬上的做法(差不太多):

m = ∑ a i m=\sum a_i m=ai

考虑 m m m 个带标号的球,第 i i i 种颜色的有 a i a_i ai 个,随便排列。

那么每种颜色中编号最大的球位置也最大的概率就是 1 ∏ a i \frac 1{\prod a_i} ai1 。(概率乘上 n ! n! n! 就是方案数了,这么说后面好理解一点)

考虑一个排列 p p p,表示每种颜色的最大位置的顺序(此后都要求最大编号在最大位置),那么形成这个排列方式的概率就是 1 ∏ i = 1 n s i \frac 1{\prod_{i=1}^n s_i} i=1nsi1 p n p_n pn 表示最大)

而现在我们想求 ∑ p 1 ∏ i = 2 n s i \sum_p \frac 1{\prod_{i=2}^ns_i} pi=2nsi1,相当于最大位置最小的那个颜色不要求编号最大的在最大位置。
那么我们枚举这个颜色 x x x,剩下的颜色只需要满足最大编号在最大位置,且最大位置大于 x x x 的最大位置,求满足条件的摆放的生成概率。

第一个限制只需要除上一个 ∏ i ≠ x a i \prod_{i\neq x} a_i i=xai,第二个限制可以钦定有多少种颜色的最大位置小于 x x x 的最大位置来容斥,假设这个颜色集合是 S S S,那么贡献就是 ( − 1 ) ∣ S ∣ ∗ a x a x + ∑ i ∈ S a i (-1)^{|S|}*\frac {a_x}{a_x+\sum_{i\in S}a_i} (1)Sax+iSaiax,后面一项是 x x x 颜色在最大位置的概率。

枚举 ∑ a i \sum a_i ai 来计算,那么就只需要知道 ∑ a i \sum a_i ai 对应的 ∑ ( − 1 ) S \sum (-1)^S (1)S,就是一个背包,可以写成生成函数 ( 1 − x a i ) (1-x^{a_i}) (1xai),算某种颜色时除去对应项即可。

复杂度 O ( n ∗ m ) = O ( n 2 max ⁡ a i ) O(n*m)=O(n^2\max a_i) O(nm)=O(n2maxai)

Code:

#include<bits/stdc++.h>
#define maxn 205
#define maxm 205*1005
using namespace std;
const int mod = 998244353;
int n,a[maxn],m,F[maxm],G[maxm],inv[maxm],Inv=1,ans;
int main()
{
	freopen("restaurant.in","r",stdin);
	freopen("restaurant.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	F[0]=1;
	for(int i=1;i<=n;i++)
		for(int j=(m+=a[i]);j>=a[i];j--)
			F[j]=(F[j]-F[j-a[i]])%mod;
	inv[0]=inv[1]=1;
	for(int i=2;i<=m;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i++) Inv=1ll*Inv*inv[a[i]]%mod;
	for(int i=1;i<=n;i++){
		int s=0;
		for(int j=0;j<=m-a[i];j++) G[j]=(F[j]+(j>=a[i]?G[j-a[i]]:0))%mod, s=(s+1ll*G[j]*inv[j+a[i]])%mod;
		s=1ll*s*a[i]%mod*a[i]%mod*Inv%mod;
		ans=(ans+s)%mod;
	}
	printf("%d\n",(ans+mod)%mod);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值