退役老狗來做CSP題了。作爲一套選拔試題,感覺涉及的知識點不是太全面。但如果單就題目來看,還是值得一做的。
格雷碼
從高位往低位做,每次通過的當前位來得到這一位的答案。同時如果這一位的答案是,則要把從前往後數變爲從後往前數。
時間複雜度.
#include<bits/stdc++.h>
typedef unsigned long long ull;
int n;
ull k,bin[105];
int main()
{
scanf("%d%llu",&n,&k);
bin[0]=1;
for (int i=1;i<n;i++) bin[i]=bin[i-1]*2;
for (int i=n-1;i>=0;i--)
{
putchar((k&bin[i])?'1':'0');
if (k&bin[i]) k-=bin[i],k=bin[i]-1-k;
}
return 0;
}
括號樹
先求出以每個節點作爲結尾有多少個合法的序列,然後再求前綴和。設當前節點上的字符是右括號,記錄以每個節點爲結尾的最長合法序列位置,找到從當前節點的父節點開始第一個無法被匹配的左括號來與當前節點匹配,則當前點的答案就是對應左括號節點的父節點答案.
時間複雜度.
#include<bits/stdc++.h>
#define pb push_back
typedef long long LL;
const int N=500005;
int n,fa[N],nx[N];
LL f[N],sum[N];
std::vector<int> e[N];
char str[N];
void dfs(int x)
{
if (str[x]==')')
{
int y=nx[fa[x]];
if (y) f[x]=f[fa[y]]+1,nx[x]=nx[fa[y]];
}
else nx[x]=x;
sum[x]=f[x]+sum[fa[x]];
for (auto to:e[x]) dfs(to);
}
int main()
{
scanf("%d",&n);
scanf("%s",str+1);
for (int i=2;i<=n;i++)
{
scanf("%d",&fa[i]);
e[fa[i]].pb(i);
}
dfs(1);
LL ans=0;
for (int i=1;i<=n;i++) ans^=(LL)sum[i]*i;
printf("%lld\n",ans);
return 0;
}
樹上的數
對於一個點,只考慮的出邊,可以發現不同的刪邊順序必然對應着不同的結果,並且不同的點之間是可以分開考慮的。
很容易想到貪心,從小到大枚舉節點上的數字,再找到這個數字能到達的最小的節點編號,並把該編號作爲答案。問題在於如何判斷一個數字能到達哪些節點。
假設把數字從移到,到路徑上的點爲,其中,則產生的限制爲
(1)這條邊一定是的所有出邊中最先被刪掉的;
(2)這條邊一定是的所有出邊中最後被刪掉的;
(3)只考慮的出邊,必然是在刪掉之後緊接着被刪掉。
不難驗證若加上所有限制後每個點均存在合法的刪邊序列,則這樣的操作是可行的。
換句話說,如果把一個節點的每條出邊都看作一個點,每個限制看成一條有向邊,則最終必然會形成若干條鏈。那麼只需要記錄每個點的入度,出度以及所在鏈的信息,就可以判斷加入一個限制後是否仍然合法。我的實現方法是並查集。
回到貪心算法,枚舉數字後,從該數字的初始位置出發dfs,找到所有能去到的點並求出最小值,再把限制全部加上即可。
時間複雜度.
#include<bits/stdc++.h>
#define pb push_back
const int N=2005;
int n,pos[N],f[N*N],ans[N],mn,top,stack[N],num[N],sz[N*N];
bool vis[N],in[N*N],out[N*N],tag[N*N],tag2[N*N];
std::vector<int> e[N];
int get(int x,int y)
{
return (x-1)*n+y;
}
int find(int x)
{
return f[x]==x?x:f[x]=find(f[x]);
}
void dfs1(int x,int fa)
{
int u=find(get(x,fa));
if (!vis[x]&&(!tag[u]||sz[u]==e[x].size())) mn=std::min(mn,x);
for (auto to:e[x])
{
if (to==fa) continue;
int v=find(get(x,to));
if (tag[u]&&tag2[v]&&sz[u]+sz[v]<e[x].size()) continue;
if (!in[get(x,to)]&&!out[get(to,x)]&&u!=v) dfs1(to,x);
}
}
bool dfs2(int x,int fa,int tar)
{
if (x==tar) {stack[++top]=x;return 1;}
for (auto to:e[x])
if (to!=fa&&dfs2(to,x,tar)) {stack[++top]=x;return 1;}
return 0;
}
void clear()
{
for (int i=1;i<=n;i++) e[i].clear(),vis[i]=0;
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&pos[i]),num[pos[i]]=i;
for (int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
e[x].pb(y);e[y].pb(x);
}
for (int i=1,p;i<=n;i++)
for (auto to:e[i])
p=get(i,to),f[p]=p,sz[p]=1,in[p]=out[p]=tag[p]=tag2[p]=0;
for (int i=1;i<=n;i++)
{
int x=pos[i];mn=n+1;
for (auto to:e[x])
{
int u=get(x,to),v=get(to,x);
if (!in[u]&&!out[v]&&(!tag2[find(u)]||e[x].size()==sz[find(u)])) dfs1(to,x);
}
ans[i]=mn;
vis[mn]=1;
top=0;
dfs2(x,0,mn);
for (int j=2;j<top;j++)
{
int u=get(stack[j],stack[j-1]),v=get(stack[j],stack[j+1]);
out[v]=in[u]=1;
u=find(u);v=find(v);f[u]=v;tag[v]|=tag[u];tag2[v]|=tag2[u];sz[v]+=sz[u];
}
tag[find(get(stack[top],stack[top-1]))]=1;
out[get(stack[1],stack[2])]=tag2[find(get(stack[1],stack[2]))]=1;
}
for (int i=1;i<=n;i++) printf("%d ",ans[i]);
puts("");
clear();
}
return 0;
}
Emiya 家今天的飯
考慮對食材的限制進行容斥,用所有方案減去恰有一種食材的使用數量超過一半的方案。枚舉這樣的食材,設表示前中烹飪方法中,使用的菜數量減去不使用的菜數量爲的方案,轉移就是個簡單的揹包。
時間複雜度.
#include<bits/stdc++.h>
typedef long long LL;
const int N=105;
const int MOD=998244353;
int n,m,a[N][N*20],s[N],f[N][N*2];
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]),(s[i]+=a[i][j])%=MOD;
int ans=1;
for (int i=1;i<=n;i++) ans=(LL)ans*(s[i]+1)%MOD;
ans--;
for (int i=1;i<=m;i++)
{
f[0][n]=1;
for (int j=1;j<=n;j++)
for (int k=-j;k<=j;k++)
{
f[j][k+n]=f[j-1][k+n];
(f[j][k+n]+=(LL)f[j-1][k-1+n]*a[j][i]%MOD)%=MOD;
(f[j][k+n]+=(LL)f[j-1][k+1+n]*(s[j]-a[j][i])%MOD)%=MOD;
}
for (int j=1;j<=n;j++) (ans-=f[n][j+n])%=MOD;
}
printf("%d\n",(ans+MOD)%MOD);
return 0;
}
劃分
大膽猜一波結論,我們肯定是讓最後那一段的值越小越好。設表示以爲結尾倒數第二段的結束位置,對於每一個的,顯然等於所有滿足中最大的。用一個單調隊列求出所有的,然後順着往回跳並統計答案就好。
時間複雜度.
#include<bits/stdc++.h>
typedef long long LL;
const int N=40000005;
int n,ty,b[N],f[N],que[N],stack[50];
LL a[N];
void write(__int128 x)
{
int top=0;
while (x) stack[++top]=x%10,x/=10;
while (top) putchar(stack[top--]+'0');
puts("");
}
void init()
{
scanf("%d%d",&n,&ty);
if (!ty)
{
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
}
else
{
int x,y,z,m;scanf("%d%d%d%d%d%d",&x,&y,&z,&b[1],&b[2],&m);
for (int i=3;i<=n;i++) b[i]=((LL)x*b[i-1]+(LL)y*b[i-2]+(LL)z)%(1<<30);
int ls=0;
while (m--)
{
int p,l,r;scanf("%d%d%d",&p,&l,&r);
for (;ls<p;ls++) a[ls+1]=b[ls+1]%(r-l+1)+l;
}
}
for (int i=1;i<=n;i++) a[i]+=a[i-1];
}
LL get(int x)
{
return a[x]*2-a[f[x]];
}
void solve()
{
int h=1,t=1;
for (int i=1;i<=n;i++)
{
while (h<t&&a[i]>=get(que[h+1])) h++;
f[i]=que[h];
while (t>=h&&get(que[t])>=get(i)) t--;
que[++t]=i;
}
__int128 ans=0;
for (int i=n;i;i=f[i]) ans+=(__int128)(a[i]-a[f[i]])*(a[i]-a[f[i]]);
write(ans);
}
int main()
{
init();
solve();
return 0;
}
樹的重心
考慮對每個點求出刪掉哪些邊後這個點會變成重心。對每棵子樹用一棵線段樹維護子樹中的。枚舉刪掉哪一棵子樹中的邊,討論刪邊後剩下的子樹中最大的是誰,在對應線段樹中查詢即可。
時間複雜度.
#include<bits/stdc++.h>
#define pb push_back
typedef long long LL;
const int N=300005;
int n,size[N],dfn[N],mx[N],tim,sz,rt[N],root,bel[N];
LL ans;
struct tree{int s,l,r;}t[N*30];
std::vector<int> e[N];
void dfs(int x,int fa)
{
size[x]=1;dfn[x]=++tim;bel[tim]=x;
for (auto to:e[x])
if (to!=fa) dfs(to,x),size[x]+=size[to];
mx[x]=tim;
}
int newnode()
{
sz++;
t[sz].s=t[sz].l=t[sz].r=0;
return sz;
}
void ins1(int &d,int l,int r,int x)
{
int p=d;d=newnode();t[d]=t[p];
t[d].s++;
if (l==r) return;
int mid=(l+r)/2;
if (x<=mid) ins1(t[d].l,l,mid,x);
else ins1(t[d].r,mid+1,r,x);
}
void ins2(int &d,int l,int r,int x,int y)
{
if (!d) d=newnode();
t[d].s+=y;
if (l==r) return;
int mid=(l+r)/2;
if (x<=mid) ins2(t[d].l,l,mid,x,y);
else ins2(t[d].r,mid+1,r,x,y);
}
int query(int d,int p,int l,int r,int x,int y)
{
if (x>y||y<l||x>r) return 0;
if (x<=l&&r<=y) return t[d].s-t[p].s;
int mid=(l+r)/2;
return query(t[d].l,t[p].l,l,mid,x,y)+query(t[d].r,t[p].r,mid+1,r,x,y);
}
int query(int d,int p,int q,int l,int r,int x,int y)
{
if (x>y||y<l||x>r) return 0;
if (x<=l&&r<=y) return t[d].s+t[p].s-t[q].s;
int mid=(l+r)/2;
return query(t[d].l,t[p].l,t[q].l,l,mid,x,y)+query(t[d].r,t[p].r,t[q].r,mid+1,r,x,y);
}
void solve(int x,int fa)
{
int mx1=n-size[x],mx2=0;
for (auto to:e[x])
{
if (to==fa) continue;
if (size[to]>mx1) mx2=mx1,mx1=size[to];
else mx2=std::max(mx2,size[to]);
}
int sum=0;
for (auto to:e[x])
{
if (to==fa) continue;
if (size[to]==mx1)
{
int l=size[to]*2-n,r=size[to]-mx2;
sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
l=size[to]-mx2+1,r=n-mx2*2;
sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
}
else
{
int l=1,r=n-mx1*2;
sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
}
}
if (n-size[x]==mx1)
{
int l=(n-size[x])*2-n,r=n-size[x]-mx2;
sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
l=n-size[x]-mx2+1,r=n-mx2*2;
sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
}
else
{
int l=1,r=n-mx1*2;
sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
}
ans+=(LL)sum*x;
for (auto to:e[x])
{
if (to==fa) continue;
ins2(root,1,n,size[to],-1);
ins2(root,1,n,n-size[to],1);
solve(to,x);
ins2(root,1,n,n-size[to],-1);
ins2(root,1,n,size[to],1);
}
}
void clear()
{
sz=root=tim=0;ans=0;
for (int i=1;i<=n;i++) e[i].clear(),rt[i]=0;
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
e[x].pb(y);e[y].pb(x);
}
dfs(1,0);
for (int i=1;i<=n;i++) rt[i]=rt[i-1],ins1(rt[i],1,n,size[bel[i]]);
for (int i=2;i<=n;i++) ins2(root,1,n,size[i],1);
solve(1,0);
printf("%lld\n",ans);
clear();
}
return 0;
}