T1万花筒
Descrption
初始 \(m\) 条边,如果 \((u,v)\) 在图中,那么将 \(((u+1)modn+1,(v+1)modn+1)\) 也加入图中。求该图的最小生成树,\(1≤n≤10^9,1≤m≤10^5\)。
Solution
对于加入每条边,考虑它对连通块的贡献,你可以从每一个点出发,看它有多少点能通过该次加边连一起,于是你可以将它视作一个环,令 \(d=u-v\),每次向前跑 \(d\)。最后跑回原来的点时路上经过的所有点就是该连通块包含的点,经过的总距离显然就是 \(lcm(d,n)\),除以 \(d\) 就是连通块点数,\(n/gcd(n,d)\),再以 \(n\) 除以它就是连通块个数,发现就是 \(gcd(n,d)\)。所以每次加入一条边,就是不断地将连通块个数变为 \(gcd(n,d)\),相当于连通块减少了 \(n-gcd(n,d)\),就将答案加上它乘以边权,将 \(n\) 改为 \(gcd(n,d)\) 即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline void read(int &res){
res=0;
int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')res=(res<<1)+(res<<3)+c-48,c=getchar();
res*=f;
}
int T;
int n,m;
struct node{
int u,v,w;
}a[100005];
inline int gcd(int aa,int bb){return bb?gcd(bb,aa%bb):aa;}
inline bool cmp(node aa,node bb){return aa.w<bb.w;}
signed main()
{
freopen("kaleidoscope.in","r",stdin);
freopen("kaleidoscope.out","w",stdout);
read(T);
while(T--){
int ans=0;
read(n);read(m);
int gd=n,cnt=n;
for(int i=1;i<=m;i++){
read(a[i].u);read(a[i].v);read(a[i].w);
}
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++){
a[i].u=a[i].u%(n+1)+1;
a[i].v=a[i].v%(n+1)+1;
if(a[i].u>a[i].v)swap(a[i].u,a[i].v);
int y=a[i].v-a[i].u;
if(!y)continue;
int xgd=gcd(gd,y);
int ygd=gcd(n,xgd);
if(ygd<cnt){
gd=xgd;
ans+=a[i].w*(cnt-ygd);
cnt=ygd;
}
if(cnt==1)break;
}
printf("%lld\n",ans);
}
return 0;
}
T2冒泡排序期望
Descrption
一个长为 \(n\) 的排列的花费是使其排好序所要冒泡的次数。
for(int i=1;i<n;i++)
if(a[i]>a[i+1]) swap(a[i],a[i+1]);
求随机一个长为 \(n\) 的排列,花费的期望。
Solution
对于每个数,每次冒泡必定能将一个大于它的数从它前面挪到后面去,所以以 \(cnt[i]\) 表示 \(i\) 一个排列中在 \(i\) 前面还比它大的数,一个排列得到的答案就是 \(max\{cnt[i]\}\)。考虑得到某个答案的排列有多少个,令得到答案 \(i\) 的排列个数为 \(f[i]\)。
顺次考虑对每一个数,将它放在哪一个位置,你为了保证贡献的答案不超过 \(k\),你给后面留的位置就一定小于等于 \(k\),所以每次你只能放当前剩余位置的第 \(1-k+1\) 上。所以就是乘上一堆 \(k+1\),最后再乘上 \(k!\)(因为最后位置不足\(k+1\)个)。但这显然是答案小于等于 \(k\) 的排列数,所以还要减去小于等于 \(k-1\) 的答案数,这样得到的才是答案恰好为 \(k\) 的排列数,因此 \(f[i]=k!((k+1)^{n-k}-k^{n-k})\)。
最后答案就为 \(\sum_{k=1}^n k*f[k]/n!\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,f=1,p=1000000007,ans,k,pa;
int qpow(int x,int y){
if(y==0)return 1;
int k=qpow(x,y>>1);
k=k*k%p;
if(y&1)k=k*x%p;
return k;
}
signed main(){
freopen("bubble.in","r",stdin);
freopen("bubble.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++){
f=f*i%p;
k=qpow(i,n-i)*f%p;
ans=(ans+(p+k-pa)*(i-1))%p;
pa=k;
}
printf("%lld",ans*qpow(f,p-2)%p);
return 0;
}
T3数点
Descrption
有一大小为 \(n*n\) 的矩形,给出排列 \(p\),表示一个图中的点\((i,p[i])\),对于所有子矩形,求出其内部包含点数的 \(k\) 次方,求和。
\(n<=10^5,k<=3\)
Solution
对于 \(k\),我们可以将它理解为任意选择 \(k\) 个点,同时包含它们的矩形的个数,对于选出的点,找出最小矩形,然后将四个方向能扩展的长度乘起来,就是单次的贡献,但显然暴力枚举是不可行的,我们分别考虑枚举到的点的形状,然后用数据结构优化处理。
例如,\(k=2\) 有两种情况。
对于第一种就可以按 \(p[i]\) 从小到大处理,然后到自身时计算所有小于自己横坐标的贡献值乘上自己向右下扩展的乘积,然后向横坐标树状数组中加入向左上扩展的乘积。
另一种类似。
\(k=3\)就有6种,可以用线段树处理,但我不会(((
Code
鸽
好吧,只写了暴力的。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
inline void read(int &res){
res=0;
int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')res=(res<<1)+(res<<3)+c-48,c=getchar();
res*=f;
}
int n,k;
int ans;
int mp[305][305],sqz[305][305],hqz[305][305];
int p[100005];
signed main()
{
freopen("points.in","r",stdin);
freopen("points.out","w",stdout);
read(n);read(k);
if(k==1){
for(int i=1;i<=n;i++){
read(p[i]);
int s=p[i],x=n-p[i]+1,z=i,y=n-i+1;
ans+=s*x%mod*z%mod*y%mod;
ans%=mod;
}
printf("%lld\n",ans);
}
if(k==2){
for(int i=1;i<=n;i++){
read(p[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int s=min(p[i],p[j]),x=n-max(p[i],p[j])+1,z=min(i,j),y=n-max(i,j)+1;
ans+=s*x%mod*z%mod*y%mod;
ans%=mod;
}
}
printf("%lld\n",ans);
}
if(k==3){
if(n>100){
cout<<"QAQ"<<endl;
return 0;
}
for(int i=1;i<=n;i++){
read(p[i]);
mp[i][p[i]]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
hqz[i][j]=hqz[i][j-1]+mp[i][j];
sqz[i][j]=sqz[i-1][j]+mp[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
for(int k=1;k<=n;k++){
int sum=0;
for(int l=k;l<=n;l++){
sum+=sqz[j][l]-sqz[i-1][l];
ans+=sum*sum*sum;
ans%=mod;
}
}
}
}
cout<<ans;
}
return 0;
}