2021百度之星復賽部分題解
T1
\(n,m\leq 2\)的情況需要特殊討論
其余的,\(m\)為奇數時是二分圖,一定成立
\(m\)為偶數時,只有\(n=m\)成立
int n,m;
int main(){
rep(_,1,rd()) {
n=rd(),m=rd();
if(n>1 && (m&1 || (n>2 && m==n))) {
puts("Yes!");
continue;
}
puts("No!");
}
}
T2
有點難度的計數題,將序列剖分成相同符號的連續段,每段個數為\(a_i\)
則按照+號和*誰開頭誰末尾分成4類討論,再枚舉分段數
即可把問題轉化為若干個這樣的子問題: 把+號分成\(x\)段,把*號分成\(y\)段
容易發現這就是盒子有序的第二類斯特林數,可以\(O(n^2)\)預處理,\(O(n)\)完成一組詢問的查詢
const int N=6010,P=1e9+7;
int n,m;
int S[N][N];
int main(){
S[0][0]=1;
rep(i,1,N-1) rep(j,1,i) S[i][j]=1ll*j*(S[i-1][j-1]+S[i-1][j])%P;
// 盒子有序的斯特林數
rep(_,1,rd()) {
n=rd(),m=rd();
int ans=0;
rep(i,1,min(n,m)+1) {
ans=(ans+2ll*S[n][i]*S[m][i]%P+1ll*S[n][i]*S[m][i-1]+1ll*S[n][i]*S[m][i+1])%P;
}
printf("%d\n",ans);
}
}
T3
貪心題真的搞心態,開始寫了一個堆貪心又T又wa
首先考慮\(b_i=1\)的簡單版本,此時容易發現就是最大化連邊的數量
每個點優先向兒子連,有多再丟給父親即可,可以用一個dfs貪心處理
一般的情況,可以做一個轉化得到一個類似的問題:
每個點有\(b_i\)個,最多連\(p_i\)次 \(\Longrightarrow\) 每個點最多連\(b_i\cdot p_i\)條邊
一條邊\((u,v)\)可以連\(\min\{b_u,b_v\}\)次
將容易發現轉化后一種連邊方案最終總可以構造得到一組合法的連邊方案
如上,同樣可以通過dfs來得到答案,為總點數-連邊數
const int N=4e5+10,INF=1e9+10;
int n,m;
struct Edge{
int to,nxt;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
ll a[N],c[N],ans;
void dfs(int u,int f) {
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
ll w=min(min(a[u],a[v]),min(c[u],c[v]));
c[u]-=w,c[v]-=w,ans-=w;
}
}
int main() {
rep(_,1,rd()) {
n=rd(),ecnt=0;
rep(i,1,n) head[i]=0;
rep(i,2,n) {
int u=rd(),v=rd();
AddEdge(u,v),AddEdge(v,u);
}
ans=0;
rep(i,1,n) {
int b=rd(),p=rd();
ans+=a[i]=b,c[i]=1ll*b*p;
}
dfs(1,0);
printf("%lld\n",ans);
}
}
T4
是一個經典的dp優化題,可以預處理樹上兩點距離
先考慮對於一個給定序列的求解,容易發現是一個經典的區間dp的問題
這類問題想要優化復雜度,最常見的辦法是決策單調性
感性理解可以直接套用四邊形不等式進行決策優化,在\(O(n^2)\)時間內求解
回到原題,發現這些dp會出現共用一部分dp答案的情況
我們給每一個元素定一個編號,原先\(dp_{l,r}\)用\(l,r\)的編號替換為\(dp_{i,j}\)
這樣每次插入一個元素,計算以這個元素\(j\)為結尾,序列前面每一個\(i\)所對應的\(dp_{i,j}\)
同時維護區間總和,區間決策點(用於決策單調性)
訪問前面每一個位置可以通過記錄一個前驅來實現,這樣就能完成共用dp答案
復雜度為\(O(m^2+(n+r)^2)\),由於數組訪問比較不連續,常數較大
const int N=1510,M=300+N,INF=1e9+10;
int n,m,q;
struct Edge{
int to,nxt,w;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v,int w) {
e[++ecnt]=(Edge){v,head[u],w};
head[u]=ecnt;
}
ll dis[N][N];
void dfs(int rt,int u,int f) {
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dis[rt][v]=dis[rt][u]+e[i].w;
dfs(rt,v,u);
}
}
ll ans[M],dp[M][M],s[M][M];
// ans為答案,dp[i][j]為區間dp數組
// s[i][j] 為區間距離總和
int g[M][M];
// g[i][j]為dp決策點
int pre[M],v[M]; // v記錄這個元素的值,pre記錄前驅
void Get(int i,int f,int x) {
pre[i]=f,v[i]=x;
dp[i][i]=s[i][i]=0,g[i][i]=-1;
ll sum=0;
for(int j=f,lst=i;~j;lst=j,j=pre[j]) {
sum+=2*dis[v[j]][x];
s[i][j]=s[f][j]+sum;
// 四邊形不等式優化dp
int l=~g[i][lst]?g[i][lst]:i,r=~g[f][j]?g[f][j]:lst;
ll mi=9e18;
g[i][j]=-1;
for(int k=l;;k=pre[k]) {
ll v=dp[i][k]+dp[pre[k]][j];
if(mi>v) mi=v,g[i][j]=k;
if(k==r) break;
}
dp[i][j]=mi+s[i][j];
ans[i]=dp[i][j];
}
}
int main(){
rep(_,1,rd()) {
n=rd(),m=rd(),q=rd();
rep(i,0,n) head[i]=0;
ecnt=0;
rep(i,1,n) {
int f=rd(),w=rd();
AddEdge(i,f,w),AddEdge(f,i,w);
}
rep(i,0,n) dfs(i,i,-1);
v[1]=rd(),pre[1]=-1,ans[1]=dp[1][1]=s[1][1]=0,g[1][1]=-1;
rep(i,2,m) Get(i,i-1,rd());
printf("%lld\n",ans[m]);
rep(i,m+1,m+q) {
int f=rd()+m,x=rd();
Get(i,f,x);
printf("%lld\n",ans[i]);
}
}
}
T5
由帶入T2代碼可以得到,最劣情況下,本質不同的方案數達到\(3\cdot 10^5\)
即便數據隨機也有大量極限情況,而且枚舉有常數,故不可以通過
實際上可以直接meet in the middle
枚舉前5個數,后五個數,方案數為\(\binom{10}{5}=252\)
再搜索兩部分的答案,第一部分帶入\(x\),第二部分初始帶入0,最壞情況下兩邊各有46種
最終的答案就是 : 第一部分答案 \(\cdot\) 第二部分乘積+第二部分答案
枚舉之后已經確定了乘積,故兩部分是分離的,只需要相加即可
相加求最小值可以尺取進行,枚舉的情況上界很松,實際極限復雜度達不到\(252\cdot 46\cdot \log\)
因此較大常數的實現也可以在1s左右出解
int n,m,ans;
char op[N];
int v[N];
int S,bin[1<<10];
int Mod(int x){
return x>=P?x-P:x;
}
int st[100000],c,st2[100000],c2;
void dfs(int S,int x,int lst1,int lst2) {
if(!S) return st[++c]=x,void();
for(int T=S;T;T&=T-1) {
int i=bin[T&-T];
if(op[i]=='+' && lst1<i) dfs(S-(1<<i),Mod(x+v[i]),i,-1);
if(op[i]=='*' && lst2<i) dfs(S-(1<<i),1ll*x*v[i]%P,-1,i);
}
}
int cnt[1<<10];
int X[6][500],Xc[6];
int Y[6][500],Yc[6];
const int A=1023;
bool Med;
int main(){
rep(i,0,9) bin[1<<i]=i;
rep(i,1,A) cnt[i]=cnt[i&(i-1)]+1;
rep(_,1,rd()) {
ans=P;
n=rd(),m=rd();
rep(i,0,n-1) {
while(op[i]=getchar(), op[i]!='*' && op[i]!='+');
v[i]=rd();
}
// 這里是我寫蠢了,直接枚舉10個數的子集,大小為5的討論就好了。。
int S=0;
rep(i,0,n-1) if(op[i]=='*') S|=1<<i;
rep(i,0,n) Xc[i]=Yc[i]=0;
for(int T=S;;T=(T-1)&S) {
if(cnt[T]>5) continue;
X[cnt[T]][++Xc[cnt[T]]]=T;
if(!T) break;
}
S=A^S;
for(int T=S;;T=(T-1)&S) {
if(cnt[T]>5) continue;
Y[cnt[T]][++Yc[cnt[T]]]=T;
if(!T) break;
}
rep(i,0,5) {
rep(a,1,Xc[i]) rep(b,1,Yc[5-i]) {
int x=X[i][a],y=Y[5-i][b];
int w=1;
rep(i,0,n-1) if(x&(1<<i)) w=1ll*w*v[i]%P;
c=0;
dfs(A^(x|y),m,-1,-1);
rep(i,1,c2=c) st2[i]=1ll*st[i]*w%P;
c=0;
dfs(x|y,0,-1,-1);
sort(st+1,st+c+1),sort(st2+1,st2+c2+1);
int p=1;
drep(i,c,1) {
cmin(ans,st[i]+st2[1]);
while(p<c2 && st[i]+st2[p]<P) p++;
cmin(ans,(st[i]+st2[p])%P);
}
}
}
printf("%d\n",ans);
}
}