兩個水題和兩個大水題的故事
四道數論題
T1 P3232 [HNOI2013]遊走
題目大意
給定一張無向圖,從\(1\)號點開始每次等概率地走向與它聯通的點,直到走到\(n\)號點為止,求在最好的對\(m\)條邊進行\(1\)~\(m\)標號方案之下,第\(i\)條邊的期望次數\(×\)它的標號之和的最小值。
\(2≤n≤500,1 \leq m \leq 125000\)
Sol
做過類似的題的話就很好想到是一道高斯消元的題。
所謂標號其實只是一個幌子,明顯是將每條邊期望經過次數排序以後按從大到小標號計算即可。
設\(f[i]\)
\(f[n]\)顯然沒有意義,因為\(n\)號點是隻進不出的。
所以可以對前\(n-1\)個點跑高斯消元
設\(g[i]\)表示第\(i\)條邊的期望經過次數。那麼有
時間複雜度\(O(n^2+mlogm)\)。
Code
#include<bits/stdc++.h> using namespace std; inline int read() { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar(); return x*f; } double abss(double x) { if(x>=0)return x; return -x; } double a[510][510]; double b[510],c[125010]; int deg[510]; struct edge { int to,next; }e[250010]; int h[510],ei=1; inline void add(int x,int y) { e[++ei]=(edge){y,h[x]}; h[x]=ei;return; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x=read(),y=read(); deg[x]++;deg[y]++; add(x,y);add(y,x); } a[1][1]=1.0; for(int i=h[1];i;i=e[i].next) { int to=e[i].to; if(to==n)continue; a[1][to]=-1.0/deg[to]; } a[1][n]=1.0; for(int i=2;i<n;i++) { a[i][i]=1.0; for(int j=h[i];j;j=e[j].next) { int to=e[j].to; if(to==n)continue; a[i][to]=-1.0/deg[to]; } } for(int i=1;i<n;i++) { int maxm=i; for(int j=i+1;j<n;j++) { if(abss(a[j][i])>abss(a[maxm][i]))maxm=j; } for(int j=1;j<n+1;j++) { swap(a[i][j],a[maxm][j]); } if(!a[i][i])return printf("No Solution\n")&0; for(int j=1;j<n;j++) { if(j==i)continue; double bei=a[j][i]/a[i][i]; for(int k=i+1;k<n+1;k++) { a[j][k]-=bei*a[i][k]; } } } for(int i=1;i<n;i++) { b[i]=a[i][n]/a[i][i]; } for(int i=2;i<=ei;i+=2) { int x=e[i^1].to,y=e[i].to; c[i/2]=1.0*b[x]/deg[x]+1.0*b[y]/deg[y]; } sort(c+1,c+m+1); double ans=0.0; for(int i=1;i<=m;i++) { ans+=c[i]*1.0*(m-i+1); } printf("%.3lf",ans); return 0; }
T2 P3214 [HNOI2011]卡農
題目大意
求出從\({1,2,…,n}\)的所有非空子集中選出\(m\)個不同的子集,滿足\(1\)~\(n\)每個數出現的次數均為偶數的方案數。
\(1 \leq n,m \leq 10^6\)
Sol
如果不看題解的話思維難度很高,我直接推只能想出來第一部分。
設\(f[i]\)表示選取\(i\)個子集的合法方案數。
很容易想到選定前\(i-1\)個子集以後,第\(i\)個子集已經確定,前\(i-1\)個子集的選擇方案有\(P_{2^n-1}{i-1}\)種。
考慮刪去其中不符合條件的部分:
1、最後第\(i\)個集合為空,說明前\(i-1\)個子集是合法的,故減去\(f[i-1]\)
2、由於前\(i-1\)個子集互不相同,考慮第\(i\)個子集與第\(j(1 \leq j < i)\)個子集相同的情況。那麼去掉這兩個集合後的取法也合法,\(j\)有\(i-1\)種選取方式,\(i\)有\(2^n-i+1\)種選取方式。故還要減去\(f[i-2]*(i-1)*(2^n-i+1)\)。
綜上可得\(f[i]=P_{2^n-1}{i-1}-f[i-1]-f[i-2]*(i-1)*(2^n-i+1)\)。
由於排列的下標始終為\(2^n-1\),因此可以直接預處理計算。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e8+7,maxn=1000010;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
return x*f;
}
int n,m;
int pp[maxn],f[maxn],erd;
inline int ksm(int x,int mi)
{
int ans=1;
while(mi)
{
if(mi&1)ans=ans*x%p;
x=x*x%p;mi>>=1;
}
return ans;
}
signed main()
{
n=read();m=read();
int err=erd=ksm(2,n);
pp[0]=1;
for(int i=1;i<=m;i++)
{
erd--;
if(erd==0)break;
pp[i]=pp[i-1]*erd%p;
}
f[0]=1;
for(int i=2;i<=m;i++)
{
f[i]=pp[i-1];
if(i)f[i]=(f[i]-f[i-1]+p)%p;
if(i>1)f[i]=(f[i]-(i-1)*(err-i+1)%p*f[i-2]%p+p)%p;
}
int jc=1;
for(int i=2;i<=m;i++)jc=jc*i%p;
jc=ksm(jc,p-2);
cout<<f[m]*jc%p;
return 0;
}
T3 CF722F Cyclic Cipher
題目大意
給定\(n\)個序列,每個序列長度為\(k_i\)且每個數\(\leq m\),每次將各個序列的頂部的數取下來放到該序列的尾部,並記錄下由新序列頂部組成的陣列。求在\(10^{100}\)次操作以後對於\(1\leq i \leq m\)在每次記錄的陣列中出現的最長連續長度。
\(1 \leq n,m \leq 10^5\;\; 1 \leq k_i \leq 40\)
Sol
賀題大法好啊
反正就是用尺取法幹就完了!
Code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
return x*f;
}
const int maxn=100010;
vector<int>a[maxn];
map<int,vector<pair<int,int> > >b;
int T[maxn],cnt[50],sk[50];
inline int gcd(int x,int y)
{
while(y)
{
int t=y;y=x%y;x=t;
}
return x;
}
inline bool check(int tid,int x)
{
for(int i=1;i<=40;i++)
{
if(cnt[i]&&(sk[i]-x)%gcd(i,tid))return false;
}
sk[tid]=x;cnt[tid]++;
return true;
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=n;i++)
{
T[i]=read();
for(int j=1;j<=T[i];j++)
{
int ls=read();
a[i].push_back(ls);
b[ls].push_back(pair<int,int>(i,j));
}
}
int l,r,ans;
for(int i=1;i<=m;i++)
{
ans=0;memset(cnt,0,sizeof(cnt));
int si=b[i].size();
for(l=0,r=0;l<si;l++)
{
for(;r<si&&b[i][r].first-b[i][l].first==r-l;r++)
{
if(!check(T[b[i][r].first],b[i][r].second))break;
}
cnt[T[b[i][l].first]]--;
ans=max(ans,r-l);
}
printf("%d\n",ans);
}
return 0;
}
T4 P4240 毒瘤之神的考驗
題目大意
多組詢問,求\(\sum_{i=1}^n\sum_{j=1}^m \varphi(ij)\)
\(1\leq T\leq 10^4\;\;1\leq n,m\leq 10^5\)
Sol
明天再來