【比賽報告】牛客OI周賽1-提高組
A.分組
將認識關係轉化為圖中的邊。dfs這張圖,對每一個沒有被訪問過的點,將它標記為源點的反色,回溯的時候統計每個點有多少同色相鄰點,個數等於2時將其顏色轉換。
#include<cstdio> #include<cstring> const int N=1e5+10,M=2e5+10; int n,m,tot,vis[N],hd[N]; struct Edge{ int v,nx; }e[M<<1]; void dfs(int u) { int cnt=0; for(int i=hd[u];~i;i=e[i].nx) if(!vis[e[i].v])vis[e[i].v]=vis[u]^3,dfs(e[i].v); for(int i=hd[u];~i;i=e[i].nx) cnt+=(vis[e[i].v]==vis[u]); if(cnt==2)vis[u]^=3; } void add(int u,int v) { e[tot].v=v; e[tot].nx=hd[u]; hd[u]=tot++; } int main() { //freopen("in.txt","r",stdin); memset(hd,-1,sizeof(hd)); scanf("%d%d",&n,&m); int u,v; for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u); for(int i=1;i<=n;i++) if(!vis[i])vis[i]=1,dfs(i); for(int i=1;i<=n;i++) printf("%d ",vis[i]); puts(""); return 0; }
總結
初看比賽結果發現這題只有1人滿分,然後就被嚇著了。在想會不會是並查集,或者在圖中找環,後來發現這裡的認識關係不會傳遞,且節點度數不超過3,肯定會存在一種解。
B.樹
學習了大佬程式碼。對著這份程式碼看了一個多小時好像有點點明白。大概就是在這頭選兩個端點在那頭選兩個端點更新答案(說了等於沒說)。選子樹內那一頭是不能在同一個子節點的子樹內部選兩個(會多佔一些邊),選子樹外那頭也得把共了邊的部分減去,保證最後往上跳之後求出來的路徑組合長度是在範圍內的。
#include<cstdio> #include<cstring> const int N=1e5+10,mod=1e9+7; int hd[N],tot,n,sz[N],dep[N],fa[N][20],a[N],l,r,b[N],ans; struct Edge{ int v,nx; }e[N<<1]; int C2(int x){return 1ll*x*(x-1)/2%mod;}//返回C(x,2) void add(int u,int v) { e[tot].v=v; e[tot].nx=hd[u]; hd[u]=tot++; } void dfs(int u) { sz[u]=1; for(int i=1;(1<<i)<=dep[u];i++)fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=hd[u];~i;i=e[i].nx) { int v=e[i].v; if(v==fa[u][0])continue; fa[v][0]=u;dep[v]=dep[u]+1;dfs(v); sz[u]+=sz[v];a[u]=(a[u]+C2(sz[v]))%mod; } a[u]=(C2(sz[u])-a[u]+mod)%mod;//在u的子樹中取2個不共路徑的端點 } void Dfs(int u) { for(int i=hd[u];~i;i=e[i].nx) { int v=e[i].v; if(fa[u][0]==v)continue; b[v]=(0ll+b[u]+a[u]-C2(sz[u])+mod-C2(n-sz[u])+mod+C2(sz[v])+C2(n-sz[v]))%mod; //在v的子樹外取兩個不共路徑的端點 Dfs(v); } } int up(int u,int d) { for(int i=19;i>=0;i--)if(d&(1<<i))u=fa[u][i]; return u; } int main() { //freopen("in.txt","r",stdin); memset(hd,-1,sizeof(hd)); scanf("%d%d%d",&n,&l,&r); int u,v; for(int i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u); dfs(1);Dfs(1); for(int i=1;i<=n;i++) { int v=up(i,l-1);int u=up(i,r);//從i往上跳 ans=(0ll+ans+(2ll*a[i]+1)*(b[v]-b[u]+mod)+1ll*a[i]*(dep[v]-dep[u]))%mod; } printf("%d\n",ans); return 0; }
總結
看了個似懂非懂,但是時間非常有限,不能再繼續看了。(我連樣例答案都推不出來)
C.序列
我們列舉不同數字的個數 。此時等價於這個問題,有 個箱子排成一排,任 意兩個箱子之間距離不超過 (超過 意味著可以把這個間距減小到 ,且是一個等價的序 列),第一個箱子和最後一個箱子的距離不超過 的方案數。設 表示放置了 個箱子,第 個箱子和最後一個箱子的距離為 的方案數。 注意要減去超過 的那些方案數。觀察到這個狀態轉移方程可以利用字首和優化至 。 將 個位置放入 種不同數字一共有 種不同的方法,其中 代表第二類斯特林數。 然後還要對 個數字進行大小定位,共有 種不同方法。 目標:
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int N=2e3+10;
int n,m,k;
ll fac[N],sum[N],f[N][N],s[N][N],ans;
int main()
{
scanf("%d%d%d",&n,&m,&k);
fac[0]=1;
for(int i=1;i<=n;i++)
{
s[i][1]=s[i][i]=1;fac[i]=(fac[i-1]*i)%mod;
for(int j=2;j<i;j++)
s[i][j]=(s[i-1][j-1]+j*s[i-1][j]%mod)%mod;
}
f[1][0]=1;
for(int i=2;i<=n;i++)
{
sum[0]=f[i-1][0];
for(int j=1;j<=m;j++)
sum[j]=(sum[j-1]+f[i-1][j])%mod;
for(int j=1;j<=m;j++)
f[i][j]=sum[j-1];
for(int j=k+1;j<=m;j++)
f[i][j]=(f[i][j]-sum[j-k-1]+mod)%mod;
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
ans=(ans+(s[n][i]*fac[i]%mod)*f[i][j]%mod)%mod;
printf("%lld\n",ans);
return 0;
}
總結
初拿到這道題毫無頭緒。這題轉化為一個放箱子的模型,通過一系列分析利用DP求解,很巧妙。
比賽總結
這三題我幾乎都沒什麼好的思路。T1難度其實並不高,但是思維走偏了。應該要充分發掘題目隱藏的性質。T2T3都是求方案數,自己在這方面比較薄弱。T2列舉端點那個地方不好懂,不過這種方法確實很好,利用倍增直接往上跳然後更新答案。T3的模型很好,把它轉化為一個放箱子的問題然後就可以動態規劃。而DP時通過觀察狀態轉移方程發現 階段全都已經計算過,可以直接維護一個字首和來優化時間複雜度。還有第二類斯特林數和階乘來計算方案數,感覺確實很複雜。 出題人是個狼人。