杂题选讲4

杂题选讲 4.

luogu8110 矩阵

分析

萌萌题。 \[ A^2_{i,j} = \sum_{i=k}^n A_{i,k} \cdot A_{k,j} = \sum_{k=1}^n a_ib_ka_kb_j = a_ib_j\Big( (\sum_{k=1}^n a_ib_i) = d\Big) \]

\[ \sum_{i=1}^n \sum_{j=1}^n A^2_{i,j} = \sum_{i=1}^n \sum_{j=1}^n a_ib_jd = \Big( \sum_{i=1}^n a_i \cdot \sum_{i=1}^n b_i \Big) d \]

归纳一下得到答案。

CODE

int n, k, sa, sb, a[N], b[N];
int fp(int a,int b) {
	int c=1;
	for(;b;a=a*a%mod,b>>=1) if(b&1) c=c*a%mod;
	return c;
}
signed main() {
	n=read(), k=read();
	for(int i=1;i<=n;++i) {
		a[i]=(read()%mod+mod)%mod;
		(sa+=a[i])%=mod;
	}
	for(int i=1;i<=n;++i) {
		b[i]=(read()%mod+mod)%mod;
		(sb+=b[i])%=mod;
	}
	if(!k) { printf("%lld\n",n); return 0; }
	int d=0;
	for(int i=1;i<=n;++i) (d+=a[i]*b[i]%mod)%=mod;
	printf("%lld\n",sa*sb%mod*fp(d,k-1)%mod);
}

luogu6599 异或

分析

按位贪心。

若答案的第 \(k\) 位为 \(1\),设 \(x\) 为序列中第 \(k\) 位为 \(1\) 的个数。不难发现它会与所有第 \(k\) 位不是 \(1\) 的数产生 \(2^k\) 的贡献。

那么总贡献为 \(2^k \cdot x \times (l-x)\),当 \(x = \lfloor \frac{l}{2} \rfloor\) 时有最大值。

CODE

int t, n, l;
const int mod=1e9+7;
void solve() {
	n=read(), l=read();
	if(n==1) { puts("0"); return; }
	int base=1ll<<40, x=l>>1, ans=0;
	while(base) {
		base>>=1;
		if(n<base) continue;
		(ans+=base*x%mod*(l-x))%=mod;
	}
	printf("%lld\n",ans);
}
signed main() {
	t=read();
	while(t--) solve();
}

luogu7714 排列排序

分析

容易想到,一定存在一种排序方法,使得每个数之多被操作 \(1\) 次(因为代价的上界为 \(n\))。

考虑这样的区间是什么样的。

设其为 \([l,r]\)。其中 \([1,l-1]\) 中的数要严格小于 \([l,r]\) 中的最小值,\([r+1,n]\) 中的数要严格大于 \([l,r]\) 中的最大值。

双指针找即可。

CODE

const int N=1e6+5;
int t, n, p[N];
void solve() {
	n=read();
	for(int i=1;i<=n;++i) p[i]=read();
	int l=1, ans=0;
	while(l<=n) {
		if(p[l]==l) ++l;
        // 跳过已经有序的部分
		else {
			int r=l+1, mx=max(p[l],p[r]);
			while(mx>r) {
                // 直到最大值和右端点相等
				++r;
				mx=max(mx,p[r]);
			}
			ans+=r-l+1;
			l=r+1;
		}
	}
	printf("%lld\n",ans);
}
signed main() {
	t=read();
	while(t--) solve();
}

luogu8161 自学 (Self Study)

分析

二分答案 \(mid\),以课程数量作为限制。

对于一个 \(i\),如果 \(b_i > a_i\),那么就没必要上课,全部自学即可。

否则贪心的多上课。如果上课就足够满足了,那么就上那么多课,否则就占用自学的课程。

如果某个时刻需要的课程数量超过了 \(nm\),那么不行。

CODE

const int N=3e5+5;
int n, m, a[N], b[N];
int cil(int x,int y) { return (x+y-1)/y; }
bool check(int x) {
	int nd=0;
	for(int i=1;i<=n;++i) {
		if(a[i]<b[i]) nd+=cil(x,b[i]);
		else {
			if(a[i]*m>=x) nd+=cil(x,a[i]);
			else nd+=m+cil(x-a[i]*m,b[i]);
		}
		if(nd>n*m) return 0;
	}
	return 1;
}
signed main() {
	n=read(), m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) b[i]=read();
	int l=1, r=1000000000000000010;
	while(l<r) {
		int mid=(l+r+1)/2;
		if(check(mid)) l=mid; else r=mid-1;
	}
	printf("%lld\n",l);
}

luogu8432 ぽかぽかの星

分析

发现直接做比较困难。

考虑从值域下手。把相加为 \(k+1\) 的数两两分组,\((1,k)\)\((2,k-1)\)\(\cdots\)

对于任意一组,至少有一个数出现的次数为 \(0\)

\(m\) 为组数。

\(2 \mid k\) 时,有正好偶数组。

枚举非全 \(0\) 的组数 \(i\),方案数 \(\binom{m}{i}\),将 \(n\)\(1\) 分配到 \(i\) 组中,每一组不能为 \(0\),方案数 \(\binom{n-1}{i-1}\),每一组有 \(2\) 种方法,方案数为 \(2^i\)

答案为 \[ \sum_{i=1}^{\min(n,m)} \binom{m}{i} \binom{n-1}{i-1} 2^i \]\(2 \nmid k\) 时,存在一个孤立的数字。

那么一次统计去掉它的方案数,一次强制选择它,累加即可。

答案 \[ \sum_{i=1}^{\min(n,m)} \binom{m}{i} \binom{n-1}{i-1} 2^i + \sum_{i=1}^{\min(n-1,m)} \binom{m}{i} \binom{n-2}{i-1} 2^i \]

CODE

const int N=5e6+5, mod=1e9+7;
int t, n, k, fac[N], inv[N], p2[N];
int fp(int a,int b) {
	int c=1;
	for(;b;a=a*a%mod,b>>=1) if(b&1) c=c*a%mod;
	return c;
}
void init() {
	fac[1]=inv[0]=p2[0]=1;
	p2[1]=2;
	for(int i=2;i<=5e6;++i) fac[i]=fac[i-1]*i%mod, p2[i]=p2[i-1]*2%mod;
	inv[N-5]=fp(fac[N-5],mod-2);
	for(int i=N-6;i;--i) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int n,int m) { return n<=m? 1:fac[n]*inv[m]%mod*inv[n-m]%mod; }
void solve() {
	n=read(), k=read();
	if(n==1) {
		printf("%lld\n",k);
		return;
	}
	int ans=0;
	if(k%2==0) {
		int m=k/2;
		for(int i=1;i<=min(n,m);++i) (ans+=C(m,i)*C(n-1,i-1)%mod*p2[i]%mod)%=mod;
	} else {
		int m=(k-1)/2;
		for(int i=1;i<=min(n,m);++i) (ans+=C(m,i)*C(n-1,i-1)%mod*p2[i]%mod)%=mod;
		for(int i=1;i<=min(n-1,m);++i) (ans+=C(m,i)*C(n-2,i-1)%mod*p2[i]%mod)%=mod;
	}
	printf("%lld\n",ans);
}
signed main() {
	init();
	t=read();
	while(t--) solve();
}

luogu5689 多叉堆

分析

套路题。

\(f_x\) 为以 \(x\) 为根的树的方案数。

对于 \(x\) 合并到 \(y\),只需要钦定 \(y\) 的根为 \(0\),随便选出 \(sz_x\) 个数放到 \(x\) 里面,都有 \(f_x\) 种方法,其他的节点的方案数为 \(f_y\),所以得到 \[ f_y = \binom{sz_y + sz_x -1}{sz_x} f_x f_y \] 用并查集维护合并操作即可。

CODE

const int N=3e5+5, mod=1e9+7;
int n, q, fa[N], sz[N], fac[N], inv[N], f[N];
int tot, h[N], to[2*N], nxt[2*N];
void add(int x,int y) {
	to[++tot]=y, nxt[tot]=h[x], h[x]=tot;
}
void addedge(int x,int y) {
	add(x,y), add(y,x);
}
int get(int x) { return x==fa[x]? x:fa[x]=get(fa[x]); }
int fp(int a,int b) {
	int c=1;
	for(;b;a=a*a%mod,b>>=1) if(b&1) c=c*a%mod;
	return c;
}
int C(int n,int m) { return fac[n]*inv[m]%mod*inv[n-m]%mod; }
void merge(int x,int y) {
	x=get(x), y=get(y);
	sz[y]+=sz[x];
	fa[x]=y;
	f[y]=(f[y]*f[x]%mod*C(sz[y]-1,sz[x])%mod)%mod;
	// do DP
}
signed main() {
	n=read(), q=read();
	for(int i=0;i<n;++i) fa[i]=i, f[i]=1, sz[i]=1;
	fac[0]=fac[1]=1, inv[0]=1;
	for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
	inv[n]=fp(fac[n],mod-2);
	for(int i=n-1;i;--i) inv[i]=inv[i+1]*(i+1)%mod;
	int lst=0;
	while(q--) {
		int op=read();
		if(op&1) {
			int x=(read()+lst)%n, y=(read()+lst)%n;
			merge(x,y);
		} else {
			int x=(read()+lst)%n;
			printf("%lld\n",lst=f[get(x)]);
		}
	}
}

杂题选讲4
https://yozora0908.github.io/2022/tititi-solution-4/
作者
yozora0908
发布于
2022年10月22日
许可协议