1.法陣
轉化題意,就是每一行有一段連續的東西,每一列有一段連續的空格,求方案數。手模一下,整個圖大概是長這個樣子
由於它是一個向內凹進去的形狀,所以它一定有一個轉折點,我們可以去列舉這個轉折點的位置求方案數,例如 \(A\) 點
其左邊區域的方案數我們以水平線分上下兩部分求,因為是方格,所以我們可以觀察到一種情況對應一個邊界線的形狀,
從這裡看,實際上從左上角到 \(A\) 的部分的方案數就是從左上角走到 \(A\) 點的不同路徑數,對四部分都求一下即可
考慮邊界,當 \(A\) 與 \(B\) 重回時的情況在 \(A\) 區域算了一次,在 \(B\) 又算了一次,所以要減去,由於方案數與 \(n,m\) 無關
所以將 \(n,m\) 互換方案數一樣,直接乘二
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
const int mod=998244353;
const int maxn=1<<12;
using namespace std;
int n,m,jc[maxn+5],jcny[maxn+5],ans=0,sum;
void pre(){
for(int i=(jc[0]=jcny[0]=jcny[1]=1)+1;i<=maxn;i++)
jcny[i]=1ll*jcny[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=maxn;i++)
jc[i]=1ll*jc[i-1]*i%mod,jcny[i]=1ll*jcny[i-1]*jcny[i]%mod;
}
int f(int x,int y)
{
// cout<<jcny[x]<<" "<<jcny[y]<<endl;
return 1ll*jc[x+y]*jcny[x]%mod*jcny[y]%mod;
}
signed main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
pre();
for(int i=1;i<=m-1;i++)
{
sum=0;
for(int j=1;j<=n-1;j++)
{
sum=(sum+1ll*f(i,j-1)%mod*f(i-1,n-j))%mod;
// cout<<c(i,j-1)*c(i-1,n-j)<<endl;
ans=(ans+1ll*sum*f(m-1-i,j)%mod*f(m-i,n-1-j)%mod)%mod;
}
}
// swap(n,m);
n^=m^=n^=m;
for(int i=1;i<=m-1;i++)
{
sum=0;
for(int j=1;j<=n-1;j++)
{
ans=(ans+1ll*sum*f(m-1-i,j)%mod*f(m-i,n-1-j)%mod)%mod;
sum=(sum+1ll*f(i,j-1)%mod*f(i-1,n-j))%mod;
}
}
cout<<(ans<<1)%mod;
return 0;
}
2. 連通塊
一個常見套路,把刪邊改為倒著加邊,讓後再維護最長直徑,可以發現詢問一個點時,離他最遠的點一定是直徑的兩個端點之一
可以自己畫個圖手模一下,反證法很好證。求的時候就可以提前用 \(dfs\) 序求一下每個點的深度和 \(lca\) ,直接求就行。
現在題目就轉化成了如果在加邊時維護直徑,對於兩個圖,他倆之間連了一條邊,那麼新直徑的端點也一定是在這兩個圖的兩個
直徑的四個點裡面去取,用上面自己證的那個結論就能證。一共6種情況,並查集維護一下連通性,再存一下端點就行
點選檢視程式碼
#include<bits/stdc++.h>
const int maxn=2e5+10;
using namespace std;
int n,m,head[maxn],to[maxn<<1],nxt[maxn<<1],tot,pu[maxn],vis[maxn];
int fa[maxn],x[maxn],y[maxn],w[maxn],la[maxn],lb[maxn];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
struct LCA
{
int dfn[maxn],tot,dep[maxn],st[20][maxn];
int get(int x,int y) {return dep[x]<dep[y]?x:y;}
void dfs(int u,int fa)
{
dfn[u]=++tot,st[0][tot]=fa; dep[u]=dep[fa]+1;
for(int i=head[u];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],u);
}
void init()
{
dfs(1,0);
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j<=n-(1<<i)+1;j++)
st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
int lca(int x,int y)
{
if(x==y) return x;
if((x=dfn[x])>(y=dfn[y])) swap(x,y);
int l=__lg(y-x);
return get(st[l][x+1],st[l][y-(1<<l)+1]);
}
int sol(int x,int y)
{
return dep[x]+dep[y]-dep[lca(x,y)]*2;
}
void merge(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
int a=find(x),b=find(y);
fa[b]=a;
int aa[5],maxx=0;
aa[1]=la[a],aa[2]=lb[a],aa[3]=la[b],aa[4]=lb[b];
for(int i=1;i<=4;i++)
{
for(int j=i+1;j<=4;j++)
{
if(sol(aa[i],aa[j])>maxx)
{
maxx=sol(aa[i],aa[j]);
la[a]=aa[i];
lb[a]=aa[j];
}
}
}
}
int ans(int x)
{
int y=find(x);
return max(sol(x,la[y]),sol(x,lb[y]));
}
}e;
int main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i,la[i]=i,lb[i]=i;
for(int i=1;i<n;i++)
{
cin>>x[i]>>y[i];
add(x[i],y[i]);
add(y[i],x[i]);
}
e.init();
for(int i=1;i<=m;i++)
{
int op,f;
cin>>op>>f;
if(op==1)
{
vis[f]=1;
w[i]=f;
}
else
{
w[i]=-f;
}
}
for(int i=1;i<n;i++)
{
if(!vis[i])
{
e.merge(x[i],y[i]);
}
}
fill(pu+1,pu+1+m,-1);
for(int i=m;i>=1;i--)
{
if(w[i]>0)
{
e.merge(x[w[i]],y[w[i]]);
}
else
{
w[i]=-w[i];
pu[i]=e.ans(w[i]);
}
}
for(int i=1;i<=m;i++)
{
if(pu[i]==-1)continue;
cout<<pu[i]<<'\n';
}
return 0;
}
A. 進擊的巨人
提前感謝學長提供的公式的 \(markdown\)
容易發現 \(0\) 是分割線,所以考慮每個分割出來塊,裡面只有 \(1,?\),其期望值為
可以發現後面括號括起來的部分可以用字首和提前預處理出來,所以整體複雜度在 \(O(nk)\) 左右
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
const int mod=998244353;
const int maxn=1e5+10;
using namespace std;
int n,k,cnt,ans,l[maxn],r[maxn],jc[100],jcny[100],sum[maxn];
int cheng[maxn];
char s[maxn];
void pre(){
for(int i=(jc[0]=jcny[0]=jcny[1]=1)+1;i<=31;i++)
jcny[i]=1ll*jcny[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=31;i++)
jc[i]=1ll*jc[i-1]*i%mod,jcny[i]=1ll*jcny[i-1]*jcny[i]%mod;
}
int qpow(int x,int y)
{
int ans=1;
while(y)
{
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
int c(int x,int y)
{
// cout<<jcny[x]<<" "<<jcny[y]<<endl;
return 1ll*jc[x]*jcny[x-y]%mod*jcny[y]%mod;
}
signed main()
{
freopen("attack.in","r",stdin);
freopen("attack.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
pre();
cin>>s+1;
int L=1,R=0;
for(int i=1;i<=n;i++)
{
if(s[i]=='0')
{
l[++cnt]=L-1,r[cnt]=R;
L=i+1,R=i;
}
else
{
R++;
}
}
if(s[n]!='0')l[++cnt]=L-1,r[cnt]=R;
// for(int t=1;t<=cnt;t++) cout<<l[t]<<" "<<r[t]<<'\n';
for(int t=1;t<=cnt;t++)
{
for(int i=l[t];i<=r[t];i++)
{
if(s[i]=='0')continue;
if(s[i]=='?')sum[i]++;
sum[i]+=sum[i-1];
}
// for(int i=l[t];i<=r[t];i++)
// cout<<i<<" "<<sum[i]<<'\n';
}
char ss=s[1];
for(int s=0;s<=k;s++)
{
int temp=0;
for(int t=(ss=='0')+1;t<=cnt;t++)
{
// cout<<t<<"!"<<'\n';
cheng[l[t]-1]=0;
for(int i=l[t];i<r[t];i++)
{
cheng[i]=(cheng[i-1]+1ll*qpow(-i,s)*qpow(2,sum[i])%mod)%mod;
// cout<<cheng[i]<<'\n';
}
for(int i=l[t]+1;i<=r[t];i++)
{
temp=(temp+1ll*qpow(i,k-s)*qpow(qpow(2,sum[i]),mod-2)%mod*cheng[i-1]%mod)%mod;
// cout<<temp<<" "<<cheng[i-1]<<"!"<<endl;
}
}
ans=(ans+temp*c(k,s)%mod+mod)%mod;
// cout<<temp<<"!"<<'\n';
}
cout<<ans;
return 0;
}
/*
3 1
01?
*/
B. Wallpaper Collection
設 \(f_{i, j}\) 表示當前考慮了前 \(i\) 行,其中第 \(i\) 行的初始位置為 \(j\) 時,最大的喜愛值之和。
轉移列舉第 \(i + 1\) 行的初始位置 \(k\) ,發現在第 \(i + 1\) 行運動的區間滿足 \(L\le \min(j, k), R\ge \max(j, k)\) ,此時轉移為:
設 \(S_{i, j} = \sum_{k = 1}^{j}a_{i, k}, T_{i, j} = \sum_{k = j}^{n} a_{i, k}\) ,那麼 \(\operatorname{sum}(i, L, R) = sum_i - S_{i, L - 1} - T_{i, R + 1}\) 。
取 \(\max\) 只需要維護 \(S, T\) 的前字尾 \(\min\) 即可。複雜度 \(O(n^3)\)
1000的資料這樣還是有點懸啊,所以考慮最佳化,對於要求的 \(f_{i + 1, k}\) 的兩維不能最佳化,這玩意也沒有啥單調性,常規最佳化
不太可行,第三維考慮與 \(k\) 的關係,直接前字尾和最佳化一下就行
點選檢視程式碼
#include<bits/stdc++.h>
#define ll long long
const int maxn=1010;
using namespace std;
int n,m,a[maxn][maxn];
ll f[maxn][maxn],sum[maxn],s[maxn][maxn],t[maxn][maxn],ans=-1e17;
ll tem[maxn],temp[maxn],mins[maxn][maxn],mint[maxn][maxn];
int main()
{
freopen("WallpaperCollection.in","r",stdin);
freopen("WallpaperCollection.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
sum[i]+=a[i][j];
}
}
// puts("<---------------------------->");
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
s[i][j]=s[i][j-1]+a[i][j];
mins[i][j]=min(mins[i][j-1],s[i][j]);
// cout<<s[i][j]<<" ";
}
// cout<<endl;
}
for(int i=1;i<=n;i++)
{
for(int j=m;j>=1;j--)
{
t[i][j]=t[i][j+1]+a[i][j];
mint[i][j]=min(mint[i][j+1],t[i][j]);
}
}
memset(f,0xcf,sizeof f);
for(int i=1;i<=m;i++)
f[0][i]=0;
for(int i=0;i<=n;i++)
{
fill(tem+1,tem+1+m,0);
fill(temp+1,temp+1+m,0);
tem[0]=temp[0]=-0x7f7f7f7f7f;
for(int j=1;j<=m;j++)
{
// cout<<mins[i+1][j-1]<<endl;
tem[j]=max(tem[j-1],f[i][j]-mins[i+1][j-1]);
}
for(int j=1;j<=m;j++)
{
f[i+1][j]=tem[j]+sum[i+1]-mint[i+1][j+1];
// cout<<f[i+1][j]<<endl;
}
for(int j=m;j>=1;j--)
{
temp[j]=max(temp[j+1],f[i][j]-mint[i+1][j+1]);
}
for(int j=m;j>=1;j--)
{
f[i+1][j]=max(f[i+1][j],temp[j]+sum[i+1]-mins[i+1][j-1]);
}
}
for(int i=1;i<=m;i++)
ans=max(ans,f[n+1][i]);
cout<<ans;
return 0;
}
/*
*/