NOI2016卷题

P1173 [NOI2016] 网格

如果我们每个点拆出周围的8联通点做,那还是不太行,因为在边界上我们可能盲目认为右边都被封死了而导致失败

****
**#x
****

如上图第二行第四列的点

所以我们拆出两层,即所有点拆一个5*5正方形出来

UOJ的hack轻轻松松!

#**.**#

这…

只需要我们所有保留点里面同一行中间没有奇怪删掉的点的点就link,否则就不link这样

我们有 d e n g y a n g t r a n g l e dengyangtrangle dengyangtrangle哥哥的建图方式:

  1. 四个角保留距离 ≤ 2 \leq 2 2的,切比雪夫距离
  2. 每个点周围八连通的
  3. 网格上下边界的,同一列有黑点
  4. 网格左右边界的,同一行有黑点

再用上上述奇怪的建边方法就能全部pass,包括 U O J h a c k UOJhack UOJhack

P4448 [AHOI2018初中组]球球的排列

容斥做法怎么做?

考虑对于某一个排列如果相邻颜色相同我们就有-1的系数,然后带着系数求总方案数和就是答案

于是考虑我们怎么带着系数算方案数

一个显然的想法是我们每个颜色分开看,每个颜色分成若干段,如果有b段而总长度为a,就有 ( − 1 ) a − b (-1)^{a-b} (1)ab 的容斥系数

因为我们每次少一段就有-1的系数贡献

然后考虑这一段如何分配,所有球有编号,在有编号之后我们需要重排列,然后确定这每一段的大小

也就相当于有 a ! ∗ ( a − 1 b − 1 ) a!*\binom{a-1}{b-1} a!(b1a1) ,相当于我们分成b段只需要b-1刀这样子qwq

然后这一段内部的我们选完了,但是怎么和之前的放在一起?

那就相当于我们这些段元素内部之间无序,然后整体有序,因为我们虽然是有编号问题,但是在这b段确定的时候就已经把这些短的编号写进去分配好了,因此他们相当于无序的只想要和其余的顺序形式正确

所以如果我们选出了k个数就乘上 1 k ! \frac{1}{k!} k!1,最后在乘上 n ! n! n!

woc我容斥写的是对的然后预处理写错了是指数对于2取模

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int P=1e9+7;
const int MAXN=3050;

inline void inc(int &x,int y) {
	x+=(y-P);
	x+=(x>>31)&P;
}
inline int add(int x,int y) {
	return inc(x,y),x;
}
inline ll ksm(ll x,ll y) {
	ll ans=1;
	while(y) {
		if(y&1)ans=ans*x%P;
		x=x*x%P;
		y>>=1;
	}
	return ans;
}
ll fac[MAXN],ifac[MAXN];
int n,a[MAXN],b[MAXN],m,C[MAXN][MAXN],f[MAXN][MAXN];
inline void init() {
	for(int i=1; i<=n; ++i) {
		int x=a[i];
		a[i]=1;
		int cnt=0;
		for(int j=2; j*j<=x; ++j) {
			if(x%j==0) {
				cnt=0;
				while(x%j==0) {
					x/=j;
					++cnt;
				}
				if(cnt&1)a[i]=a[i]*j;
			}
		}
		a[i]=a[i]*x;
	}
	sort(a+1,a+n+1);
	for(int i=1,j=1; i<=n; i=j+1) {
		j=i;
		while(a[j+1]==a[i])++j;
		b[++m]=j-i+1;
	}
	sort(b+1,b+m+1);	fac[0]=1;
	for(int i=1; i<=n; ++i)fac[i]=fac[i-1]*i%P;
	ifac[n]=ksm(fac[n],P-2);
	for(int i=n-1; i>=0; --i)ifac[i]=ifac[i+1]*(i+1)%P;
	C[0][0]=1;
	for(int i=1; i<=n; ++i) {
		C[i][0]=1;
		for(int j=1; j<=i; ++j) {
			C[i][j]=add(C[i-1][j-1],C[i-1][j]);
		}
	}
	return ;
}

inline void solve() {
	int s=0;
	f[0][0]=1;
	for(int i=1; i<=m; ++i) {
		s+=b[i];
		for(int j=1; j<=s; ++j) {
			for(int k=1; k<=min(b[i],j); ++k) {
				if((b[i]-k)&1) {
					inc(f[i][j],P-1ll*f[i-1][j-k]*ifac[k]%P*C[b[i]-1][k-1]%P*fac[b[i]]%P);
				} else {
					inc(f[i][j],1ll*f[i-1][j-k]*ifac[k]%P*C[b[i]-1][k-1]%P*fac[b[i]]%P);
				}
			}
		}
	}
	int ans=0;
	for(int i=m; i<=s; ++i) {
		inc(ans,1ll*f[m][i]*fac[i]%P);
	}
	printf("%d\n",ans);
	return ;
}

int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; ++i)scanf("%d",&a[i]);
	init();
	solve();
	return 0;
}

P1117 [NOI2016] 优秀的拆分

太牛了!

咋做95呢

枚举AA的贡献到右端点上去,然后枚举BB再去贡献到左端点上去然后枚举中间的中心点再去分立计算两侧即可,而计算AA,BB数组可以枚举长度并哈希

时间复杂度 O ( n 2 ) O(n^2) O(n2)

怎么做100呢?

加速计算 A A AA AA B B BB BB数组就好了!

考虑任意长度为 l e n len len A A A段拼成 A A AA AA段,可以考虑经典套路,每len段插一个点,然后计算插点后所有AA的信息贡献一下!

那会发现我们所有 % L e n \%Len %Len同余的开始端点在接上一个长度为 L e n Len Len的串之后还是同余的,那么我们可以考虑用这个性质做

考虑对于某一段端点和他的上一段端点求LCP和LCS,然后对于LCP和上一段的LCS的交集,那你会发现这一段交集向前平移得到的KaTeX parse error: Can't use function '\~' in math mode at position 4: i-1\̲~̲i-2段区间中的信息正好就是我们所有可行的BB开头点,而向后平移得到的KaTeX parse error: Can't use function '\~' in math mode at position 2: i\̲~̲i+1段区间中的信息也正好就是我们所有可行的AA开头点,然后就需要一个区间加法对于AA和BB数组

差分即可,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),瓶颈在于求LCS,LCP QAQ

P1712 [NOI2016] 区间

被降智了

我看了一眼"覆盖次数 ≥ m \geq m m…"就懂了

做法就是直接在长度排序后的数组区间上尺取,然后判断去有没有一个时刻某个位置覆盖超过 m m m次,就没了真没了…

P1587 [NOI2016] 循环之美

考虑小数的循环节长度为 l l l,然后 k k k进制下小数则是乘上 k l k^l kl,那么我们有
$$
\frac{x}{y}-\lfloor\frac{x}{y}\rfloor=\frac{xk^l}{y} -\lfloor\frac{xk^l}{y}\rfloor

$$
考虑乘上y

x − ⌊ x y ⌋ ∗ y = x k l − ⌊ x k l y ⌋ ∗ y x-\lfloor\frac{x}{y}\rfloor*y={xk^l}-\lfloor\frac{xk^l}{y}\rfloor*y xyxy=xklyxkly

于是我们考虑这个东西也知道的!对于y取模
x − x k l = 0 m o d    y x-xk^l=0\mod y xxkl=0mody
显然我们有x和y互质,因为是最简分数

然后我们有
k l ≡ 1 m o d    y k^l\equiv 1\mod y kl1mody
k和y互质

那么相当于统计

∑ ∑ [ ( i , j ) = = 1 ] [ ( k , j ) = = 1 ] \sum \sum [(i,j)==1][(k,j)==1] [(i,j)==1][(k,j)==1]

这个式子也不简单…

第一种推法是我们要深刻意识到 [ ( k , j ) = = 1 ] [(k,j)==1] [(k,j)==1]的强大

而且还要知道 ∑ i = 1 n [ ( i , k ) = = 1 ] \sum_{i=1}^{n}[(i,k)==1] i=1n[(i,k)==1]O1算法,就是 n k ϕ ( k ) + f ( n m o d    k ) \frac{n}{k}\phi(k)+f(n\mod k) knϕ(k)+f(nmodk)

而对于第二类 S i S_i Si函数的求和,我们要想办法添加一下 gcd ⁡ = = 1 \gcd==1 gcd==1从而化简成更小的情况,而不是一步解决出来!

P1721 [NOI2016] 国王饮水记

有一车结论,我们挨个看吧:

定理1:小于 h 1 h_1 h1的城市没用

定理2:所有操作均需要包含城市1

定理3:所有操作在满足次数限制条件的情况下一定优先选择两个两个合并,且从小到大合并

这个定理不难感性看出,如果从大到小我们甚至连次大值可能都超不过

定理4:每次选择的城市的水量一定大于1城市水量

定理5:一定选择排序后连续的一段区间

为什么?假设我们没有选择一段区间,那么对于该决策我们可以去掉最小的一个城市,然后加入某一段中间的一个城市,这会使得答案更优秀

定理6:任意两次相邻操作区间中没有间隔

一旦有间隔我们可以将小的一侧整体向后移动

那观察形态一定是选择了若干段区间为最后的操作序列,而且按照顺序操作

f i , j f_{i,j} fi,j表示前 i i i个然后使用了j次1号城市平均值为啥

转移为 f i , j = max ⁡ k f k , j − 1 + s i − s k i − k + 1 f_{i,j}=\max_k \frac{f_{k,j-1}+s_i-s_k}{i-k+1} fi,j=maxkik+1fk,j1+sisk

想办法把转移的复杂度优化掉?

斜率优化?

我们考虑有点 ( s i , i ) ( s k − f k , j − 1 , k − 1 ) (s_i,i)(s_k-f_{k,j-1},k-1) (si,i)(skfk,j1,k1),我们希望最大化第一个点到第二个点集的斜率

显然在第二个点集形成的凸包上二分即可

定理7:dp具有决策单调性

我也不知道为啥qwq打表可知?

直接写斜率优化的单调队列就好了

接下来,我们发现 h i h_i hi互不相同!

于是:

  1. 每一次操作区间长度不会比上一次操作的区间长度更长
  2. 不为1的操作不会超过 log ⁡ n h H \log \frac{nh}{H} logHnh

第二个定理相当于我们操作序列的形态都告诉你:先是一些比较长的区间操作,然后接下来一些相邻的位置合并操作

P1999 高维正方体

算若干维的贡献,如果要多出一个m维立方体,相当于我在这m维中都填上1,其余维任意

用exgcd求逆元是要小心求出来的解是负数的情况,因此要+P%P处理一下最后算出来的解

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int P=1e9+7;
const int MAXN=1e6+7;
int n,m;
ll fac[MAXN],ifac[MAXN],k2[MAXN];
inline ll exgcd(ll a,ll b,ll &x,ll &y) {
	if(!b) {
		x=1,y=0;
		return a;
	}
	ll g=exgcd(b,a%b,x,y);
	ll t=x;
	x=y;
	y=t-(a/b)*y;
	return g;
}

inline ll getinv(ll a) {
	ll x=0,y=0;
	exgcd(a,P,x,y);
	x=(x+P)%P;
	return x;
}

int main() {
	scanf("%d%d",&n,&m);
	if(m>n) {
		puts("0");
		return 0;
	}
	fac[0]=ifac[0]=1;
	for(int i=1; i<=n; ++i)fac[i]=fac[i-1]*i%P;
	ifac[n]=getinv(fac[n]);
	for(int i=n-1; i>=1; --i)ifac[i]=ifac[i+1]*(i+1)%P;
	k2[0]=1;
	for(int i=1; i<=n; ++i)k2[i]=k2[i-1]*2%P;
	printf("%lld\n",fac[n]*ifac[m]%P*ifac[n-m]%P*k2[n-m]%P);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值