排水系統(water)
-
先把前 \(m\) 個的權值設為 \(1\)。
-
拓撲排序,將它的權值平分給它所有的出點。
-
輸出所有出度為 \(0\) 的點的權值。
-
這個思路沒有錯,但題目里說起點到終點中間點數不超過 \(10\),也就是分母可以達到 \(60^{11}\)。
-
這樣就要寫高精度,但是高精度 \(\gcd\) 不是一件容易的事情。
-
有這么一種處理方法:欽定所有權值的分母都為 \(60^{11}\),到最后再約分。因為分母一定是 \(60^{11}\) 的約數。
-
那么,過程中只要做高精度除單精度,高精度加高精度即可。
-
最后約分的話,因為分母是 \(2^{22}\times 3^{11}\times 5^{11}\),直接判斷原數是不是 \(2,3,5\) 的倍數,試除即可。
-
高精度可以直接壓 \(10^{11}\) 進制,那么這個高精度數的四則運算是 \(O(1)\) 的。(只需兩個 \(\texttt{long long}\) 存)
-
代碼並不長。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll lim=100000000000ll;//1e11
int n,m,d[110000],ind[110000],v[110000][6];
queue<int> que;
struct node{
//x%lim, x/lim
ll v[2];
node(){
v[0]=0ll; v[1]=0ll;
}
void work(){
v[0]=0ll; v[1]=362797056ll;
}
void div(int x){
v[0]=((v[1]%x)*lim+v[0])/x;
v[1]=v[1]/x;
}
void add(const node &x){
v[0]+=x.v[0]; v[1]+=x.v[1];
if (v[0]>=lim) v[0]-=lim,v[1]++;
}
bool check2() const{ return !(v[0]&1);}
bool check3(){ return !((v[0]+v[1])%3);}
bool check5(){ return !(v[0]%5);}
void print(){
if (v[1]) printf("%lld%011lld",v[1],v[0]);
else printf("%lld",v[0]);
}
} a[110000];
void print(node x){
node y; y.work();
if (!x.v[0]&&!x.v[1]){ puts("0 1"); return;}//這里其實不用特判
for (int i=1;i<=22;i++)
if (x.check2()){
x.div(2);
y.div(2);
} else break;
for (int i=1;i<=11;i++)
if (x.check3()){
x.div(3);
y.div(3);
} else break;
for (int i=1;i<=11;i++)
if (x.check5()){
x.div(5);
y.div(5);
} else break;
x.print(); putchar(' '); y.print(); putchar('\n');
}
int main(){
// freopen("water.in","r",stdin);
// freopen("water.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) a[i].work();
for (int u=1;u<=n;u++){
scanf("%d",&d[u]);
for (int i=0;i<d[u];i++){
scanf("%d",&v[u][i]);
ind[v[u][i]]++;
}
}
for (int i=1;i<=n;i++)
if (!ind[i]) que.push(i);
while (!que.empty()){
int u=que.front(); que.pop();
if (!d[u]) continue;
a[u].div(d[u]);
for (int i=0;i<d[u];i++){
a[v[u][i]].add(a[u]);
ind[v[u][i]]--;
if (!ind[v[u][i]]) que.push(v[u][i]);
}
}
for (int i=1;i<=n;i++)
if (!d[i]) print(a[i]);
return 0;
}
字符串匹配(string)
- 枚舉 \(AB\) 的長度 \(len\)。用桶維護當前 \(F(A)=0,1,2,\cdots,26\) 有多少個。
- 在從左往右掃的過程中,二分求出 \(i_{\max}\) 表示最大的 \(i\) 滿足原串可以表示為 \((AB)^iC\)。(\(AB\) 的長為 \(len\))
- 那么,對於 \(i=1,3,5,\cdots\),它們對應的 \(F(C)\) 都一樣;對於 \(i=2,4,6,\cdots\),它們對應的 \(F(C)\) 也一樣。
- 可以直接求前綴和計算答案,但是維護前綴和不能用樹狀數組,這樣會多一個 \(\log\)。可以用兩個指針分別維護 \(i\) 為奇數和偶數的前綴和,每次,奇數的指針,\(F(C)\) 的變化量為 \(1\),偶數的指針變化量為 \(0/2\),所以是常數復雜度。
- 總時間復雜度為 \(O(T\sum_{i=1}^n \log(\frac n i))=O(Tn)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Mod1=998244353;
const int Mod2=1e9+7;
const int bas1=233;
const int bas2=23333;
int T,n,tot[31],num[31],F[1100000]; char s[1100000];
ll hs0[1100000],hs1[1100000],p0[1100000],p1[1100000],ans;
inline bool check(int l0,int r0,int l1,int r1){
return (hs0[r0]+(Mod1-p0[r0-l0])*hs0[l0])%Mod1==(hs0[r1]+(Mod1-p0[r1-l1])*hs0[l1])%Mod1
&& (hs1[r0]+(Mod2-p1[r0-l0])*hs1[l0])%Mod2==(hs1[r1]+(Mod2-p1[r1-l1])*hs1[l1])%Mod2;
}
int pos0,s0,pos1,s1;
inline void add(int x){
num[x]++;
if (x<=pos0) s0++;
if (x<=pos1) s1++;
}
inline int gets0(int x){
while (pos0<x) pos0++,s0+=num[pos0];
while (pos0>x) s0-=num[pos0],pos0--;
return s0;
}
inline int gets1(int x){
while (pos1<x) pos1++,s1+=num[pos1];
while (pos1>x) s1-=num[pos1],pos1--;
return s1;
}
void initF(){
for (int i=0;i<=26;i++) tot[i]=0;
int tmp=0;
for (int i=n;i>=1;i--){
tmp-=tot[s[i]-'a'];
tot[s[i]-'a']^=1;
tmp+=tot[s[i]-'a'];
F[i]=tmp;
}
}
void solve(){
pos0=0; s0=0; pos1=0; s1=0;
for (int i=0;i<=26;i++) tot[i]=0,num[i]=0;
int tmp=1; tot[s[1]-'a']=1;
ans=0;
for (int i=2;i<n;i++){
add(tmp);
tmp-=tot[s[i]-'a'];
tot[s[i]-'a']^=1;
tmp+=tot[s[i]-'a'];
int l=0,r=(n-1)/i-1;
while (l<r){
int mid=(l+r+1)>>1;
if (check(0,mid*i,i,(mid+1)*i)) l=mid;
else r=mid-1;
}
ans+=gets0(F[i+1])*(r/2+1);
if (2*i+1<=n) ans+=gets1(F[2*i+1])*((r+1)/2);
}
}
int main(){
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
p0[0]=1; p1[0]=1;
for (int i=1;i<=1000005;i++) p0[i]=p0[i-1]*bas1%Mod1;
for (int i=1;i<=1000005;i++) p1[i]=p1[i-1]*bas2%Mod2;
scanf("%d",&T);
while (T--){
scanf("%s",s+1); n=(int)strlen(s+1);
for (int i=1;i<=n;i++){
hs0[i]=(hs0[i-1]*bas1+(s[i]-'a'+1))%Mod1;
hs1[i]=(hs1[i-1]*bas2+(s[i]-'a'+1))%Mod2;
}
initF(); solve();
printf("%lld\n",ans);
}
return 0;
}
移球游戲(ball)
- 分治。假設現在 \([l,r]\) 的球在 \([l,r]\) 的柱子上,要讓 \([l,mid]\) 的球在 \([l,mid]\) 的柱子上。
- 如果可以使得 \([l,mid]\) 中那些 \([mid+1,r]\) 的球放到當前堆的最上面,\([mid+1,r]\) 中那些 \([l,mid]\) 的球放到當前堆的最上面,那么就可以通過 \(n+1\) 堆兩兩交換,滿足條件。
- 有一種方法,在不改變其它堆的球情況下,把當前堆所有“要移動的球”移到最上面。
- 具體做法,先隨便取一個不為當前堆的臨時堆 \(tmp\):
- 統計當前堆要放到最上面的球的個數 \(a\)。
- 判斷是否需要移動,即是否要移的已經在最上面(其實這步也可以不需要)。
- 把 \(tmp\) 中移 \(a\) 個球到 \(n+1\)。(現在 \(tmp\) 空余 \(a\) 個位置,\(n+1\) 空余 \(m-a\) 個位置)
- 將當前堆的球依次移出,其中“要移動的球”放到 \(tmp\),其它球放到 \(n+1\),直到當前堆為空。(現在 \(tmp\) 和 \(n+1\) 滿了)
- 將 \(n+1\) 中最上面的 \(m-a\) 個球移回當前堆。(這些球顯然是原來當前堆的球,\(n+1\) 還剩 \(a\) 個球)
- 將 \(tmp\) 中最上面的 \(a\) 個球移回當前堆。(這些球顯然是原來當前堆的球,\(tmp\) 還剩 \(m-a\) 個球,當前堆滿了)
- 將 \(n+1\) 的 \(a\) 個球移回當前堆。(這些球顯然是原來當前堆的球,\(tmp\) 滿了,\(n+1\) 空了)
- 分析一下可以發現,當前堆要移的球到了當前堆的最上面,其它堆沒有變化(\(tmp\) 中的球也是按移出的順序移回的)。
- 之后,就可以枚舉右邊的每一個堆,讓它和左邊的堆的球交換即可。這可以簡單的實現。
- 那么就可以分治下去做這個過程了。當 \(l=r\) 時,則代表顏色為 \(l\) 的球已經在柱子 \(l\) 上,直接返回即可。
- 操作次數/時間復雜度為 \(O(n\log n\times m)\)。
#include<bits/stdc++.h>
using namespace std;
int n,m,a[55][410],sz[55],tot[55];
int ans,Ansx[1100000],Ansy[1100000];
void mymove(int x,int y,int k=1){
while (k--){
ans++; Ansx[ans]=x; Ansy[ans]=y;
sz[y]++;
a[y][sz[y]]=a[x][sz[x]];
sz[x]--;
}
}
void work(int x,int y){
if (tot[x]>tot[y]) swap(x,y);
mymove(y,n+1,tot[y]);
mymove(x,y,tot[x]);
mymove(n+1,x,tot[x]);
mymove(n+1,y,tot[y]-tot[x]);
tot[y]-=tot[x]; tot[x]=0;
}
void solve(int l,int r){
if (l==r) return;
int mid=(l+r)>>1,tmp;
for (int i=l;i<=r;i++){
tmp=(i==l?r:l); tot[i]=0;
for (int j=m;j>=1;j--)
if ((i<=mid)!=(a[i][j]<=mid)) tot[i]++;
mymove(tmp,n+1,tot[i]);
for (int j=m;j>=1;j--)
if ((i<=mid)!=(a[i][j]<=mid)) mymove(i,tmp);
else mymove(i,n+1);
mymove(n+1,i,m-tot[i]); mymove(tmp,i,tot[i]); mymove(n+1,tmp,tot[i]);
}
int j=l;
for (int i=mid+1;i<=r;i++)
while (tot[i]){
while (j<=mid&&!tot[j]) j++;
work(i,j);
}
solve(l,mid); solve(mid+1,r);
}
int main(){
// freopen("ball.in","r",stdin);
// freopen("ball.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
sz[i]=m;
}
solve(1,n);
printf("%d\n",ans);
for (int i=1;i<=ans;i++) printf("%d %d\n",Ansx[i],Ansy[i]);
return 0;
}
微信步數(walk)
- 首先,如果在第一輪或第二輪直接走完,那么模擬即可。
- 如果模擬兩輪之后沒有走完,記第一輪結束時,每一維合法的坐標數分別為 \(a_i\),第二輪結束時,每一維合法的坐標數分別為 \(b_i\),那么第 \(k(k\geq 2)\) 輪結束后,每一維合法的坐標數分別為 \(b_i-(a_i-b_i)\times k\)。
- 那么,枚舉 \(i\),表示現在計算在第 \(t+2(t\geq 1)\) 輪(滿足 \(t+2\) 輪后仍合法)第 \(i\) 步之后的答案之和。
- 然后式子就類似於 \(\sum_{x=1}^t\prod_j (tot[j][i]-(a[j]-b[j])*x)\)。其中 \(tot[j][i]\) 表示第一輪做完后,第二輪做了 \(i\) 步,第 \(j\) 維剩下的合法坐標數。
- 這個 \(t\) 是可以計算出來的,即 \(\min_{j,b[j]\neq 0} \frac{a[j][i]}{b[j]}\)。
- 將 \(x\) 看做一個變量,求出 \(f(x)=\prod_j (tot[j][i]-(a[j]-b[j])*x)\) 的表達式,設其為 \(f(x)=\sum_i c_ix^i\),那么答案即為 \(\sum_i c_i(\sum_{x=1}^tx^i)\),后面這個是個自然數冪之和的形式。
- 當 \(k\leq 3\) 時,自然數冪和有簡潔的式子。
- 當 \(k>3\) 時,那么 \(t\leq \max w_i=10^6\),可以直接預處理自然數冪和。
- 總時間復雜度 \(O(nk^2)\),可以優化求 \(f(x)\) 的過程(即每次的變化量極少)可以做到 \(O(nk)\)。
- 只給出 \(O(nk^2)\) 的實現。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int Mod=1e9+7;
const int inv2=(Mod+1)/2;
const int inv6=(Mod+1)/6;
int n,k,w[1100000],c[1100000],d[1100000];
int b[1100000],a[11][1100000];
int L[1100000],R[1100000],l[1100000],r[1100000];
ll ans,num[11][1100000],f[1100000],g[1100000];
ll qpow(ll x,int a){
ll res=1;
while (a){
if (a&1) res=res*x%Mod;
x=x*x%Mod; a>>=1;
}
return res;
}
inline ll sqr(ll x){ return x*x%Mod;}
ll calc(int k,ll x){//1^k+2^k+3^k+...+x^k
switch (k){
case 0: return x;
case 1: return (x*(x+1)%Mod*inv2%Mod);
case 2: return (x*(x+1)%Mod*(x<<1|1)%Mod*inv6%Mod);
case 3: return sqr(x*(x+1)%Mod*inv2%Mod);
default: return num[k-4][x];
}
}
void init(){
ll tmp;
for (int i=1;i<=1000000;i++){
tmp=sqr(i); tmp=sqr(tmp);
num[0][i]=(num[0][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[1][i]=(num[1][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[2][i]=(num[2][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[3][i]=(num[3][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[4][i]=(num[4][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[5][i]=(num[5][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
num[6][i]=(num[6][i-1]+tmp)%Mod;
}
}
bool flag;
int main(){
// freopen("walk.in","r",stdin);
// freopen("walk.out","w",stdout);
init();
scanf("%d%d",&n,&k); ll now=1;
for (int i=1;i<=k;i++){
scanf("%d",&w[i]);
L[i]=1; R[i]=w[i];
now=now*w[i]%Mod;
}
for (int i=1;i<=n;i++) scanf("%d%d",&c[i],&d[i]);
bool flag=false;
ans=now;
for (int i=1;i<=n;i++){
now=now*qpow(R[c[i]]-L[c[i]]+1,Mod-2)%Mod;
L[c[i]]+=d[i]; R[c[i]]+=d[i];
L[c[i]]=max(L[c[i]],1); R[c[i]]=min(R[c[i]],w[c[i]]);
if (L[c[i]]>R[c[i]]){
flag=true;
break;
}
now=now*(R[c[i]]-L[c[i]]+1)%Mod;
ans=(ans+now)%Mod;
}
if (flag){
printf("%lld\n",ans);
return 0;
}
for (int i=1;i<=k;i++) b[i]=R[i]-L[i]+1;
flag=false;
for (int i=1;i<=n;i++){
now=now*qpow(R[c[i]]-L[c[i]]+1,Mod-2)%Mod;
L[c[i]]+=d[i]; R[c[i]]+=d[i];
L[c[i]]=max(L[c[i]],1); R[c[i]]=min(R[c[i]],w[c[i]]);
if (L[c[i]]>R[c[i]]){
flag=true;
break;
}
now=now*(R[c[i]]-L[c[i]]+1)%Mod;
for (int j=1;j<=k;j++) a[j][i]=R[j]-L[j]+1;
ans=(ans+now)%Mod;
}
if (flag){
printf("%lld\n",ans);
return 0;
}
for (int i=1;i<=k;i++){
b[i]-=(R[i]-L[i]+1);
if (b[i]) flag=true;
}
if (!flag){
puts("-1");
return 0;
}
for (int i=1;i<=n;i++){
//\prod_j a[j][i]-b[j] + \prod_j a[j][i]-b[j]*2 + ... + a[j][i]-b[j]*t
//a[j][i]-b[i]*t=0 ==> t=a[j][i]/b[j]
int tmp=INF;
for (int j=1;j<=k;j++)
if (b[j]) tmp=min(tmp,a[j][i]/b[j]);
//\prod_j a[j][i]-b[j]*x
f[0]=1; for (int j=1;j<=k;j++) f[j]=0;
for (int j=1;j<=k;j++){
for (int t=0;t<j;t++){
g[t]=(g[t]+f[t]*a[j][i])%Mod;//long long !!!
g[t+1]=(g[t+1]+f[t]*(Mod-b[j])%Mod)%Mod;
}
for (int t=0;t<=j;t++) f[t]=g[t],g[t]=0;
}
for (int j=0;j<=k;j++) ans=(ans+f[j]*calc(j,tmp))%Mod;//long long !!!
}
printf("%lld\n",ans);
return 0;
}