5.26NOI模拟赛2

6 篇文章 0 订阅
2 篇文章 0 订阅

hs的太牛逼了……

搞成现在这德行我也好意思写blogTAT

T1 recompile 

题目大意:(背景太高大上了……)

release(x):将节点x到root染成新的颜色

recenter(x):换root

query(x):查询x的子树的到根的平均颜色数

(先坑 有时间写 LCT 今天刚过模板题TAT

T2 一个n的全排列,要求不存在大小为1的环,若存在大小为k的环,则对ans[k]贡献为1,求sigma ans[i] (n<=500000)

考场上当然没想出来,出考场听说了错排……

然后听ac的讲了讲并没有听懂……

和妹子和学弟研究了一下,yy出了一个……也差不多吧……

首先,一个大小为k的环的方案数为k!/k=(k-1)!

对于n

g[i,j]表示大小为i的环至少出现j个的方案数

f[i]表示i的错排

g[i,1]=C(n,i)*(i-1)!*f[n-i]

g[i,2]=C(n,i)*(i-1)!*f[n-i]*C(n-i,i)*(i-1)!*f[n-2*i]

……

如果在规定时间内求出来,那么根据容斥原理 ans[i]=g[i,1]-g[i,2]+g[i,3]……

然后考虑时间复杂度,类似筛法求素数,均摊O(nlogn)

错排,阶乘,逆元都可以O(n)预处理

#include<cstdio>
#include<cstring>
#define N 500005
#define Mo (int)(1e9+7)
#define ll long long
using namespace std;
int n;
ll f[N],fac[N],inv[N];
ll ans;
ll pow(ll a,ll b,ll p){
	ll base=a,res=1;
	while(b){
		if(b&1) res=res*base%p;
		base=base*base%p;
		b=b>>1;
	}
	return res;
}
ll C(int n,int m){
	ll tmp=fac[n]*inv[m]%Mo;
	tmp=tmp*inv[n-m]%Mo;
	return tmp;
}
void init(){
	scanf("%d\n",&n);
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%Mo;
	inv[n]=pow(fac[n],Mo-2,Mo);
	inv[0]=1;
	for(int i=n-1;i;i--) inv[i]=inv[i+1]*(i+1)%Mo;
	f[0]=1;f[1]=0;f[2]=1;
	for(int i=3;i<=n;i++) f[i]=(i-1)*(f[i-1]+f[i-2])%Mo;
}
void solve(){
	ans=0;
	ll last;
	for(int i=2;i<=n;i++){
		last=1;
		for(int j=1;j<=(int)n/i;j++){
			ll tmp=C(n-(j-1)*i,i)*fac[i-1]%Mo;
			if(j&1) ans+=f[n-j*i]*tmp%Mo*inv[j]%Mo*last%Mo;
			else ans-=f[n-j*i]*tmp%Mo*inv[j]%Mo*last%Mo;
			ans=ans%Mo;
			last=last*tmp%Mo;			
		}
	}
	printf("%lld\n",ans);
}
int main(){
	freopen("cirperm.in","r",stdin);
	freopen("cirperm.out","w",stdout);
	init();
	//for(int i=1;i<=n;i++) printf("%lld\n",f[i]);
	solve();
	return 0;
}

C.题目描述从一个全排列转成了一个逆序图,即一个逆序对连边。然后求这个图的既是独立集有事覆盖集的集合的个数。(n<=1000)

去年在八十做过

当时没做出来 但题解还是看了哒

然后这次就一眼喽……

转成序列,就是O(m)扫一遍,然后求极大上升子序列个数。n^2 dp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int Mo=(int)1e9+7;
const int N=1005;
const int M=1000005;
int left_smaller[M],right_smaller[M],a[N],dp[N];
int n,m,Max,ans;
int main(){
	freopen("senritsu.in","r",stdin);
	freopen("senritsu.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) left_smaller[i]=i-1;
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		if(x>y) swap(x,y);
		left_smaller[y]--;
		right_smaller[x]++;
	}
	for(int i=1;i<=n;i++) a[i]=left_smaller[i]+right_smaller[i]+1;
	//for(int i=1;i<=n;i++) printf("%d ",a[i]);
	for(int i=1;i<=n;i++){
		Max=0;
		for(int j=i-1;j>=1;j--)
			if(a[j]>Max && a[i]>a[j]){
				dp[i]+=dp[j];
				dp[i]%=Mo;
				Max=a[j];
			}
		if(dp[i]==0) dp[i]=1;
	}
	//for(int i=1;i<=n;i++) printf("%d ",dp[i]);
	Max=0;ans=0;
	for(int i=n;i>=1;i--)
		if(a[i]>Max){
			ans+=dp[i];
			ans%=Mo;
			Max=a[i];
		}
	printf("%d\n",ans);
	return 0;
}



                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值