10.14 正睿做題筆記
連續兩場掉分,心情非常不爽。
T1
水題,直接拿 bitset 優化轉移就可以過掉這道題。
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 26 #define M number using namespace std; const int INF=0x3f3f3f3f; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } bitset<25> bit[N]; int n,m,f[1<<N],cnt[N*N+100]; inline int Lowbit(int x){return x&(-x);} int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); // dd d1=clock(); read(n);read(m); for(int i=1;i<=m;i++){ int from,to;read(from);read(to); from--;to--; bit[from][to]=1;bit[to][from]=1; } int maxx=(1<<n)-1; for(int s=1;s<=(1<<n)-1;s++){ int now=Lowbit(s); int t=s-now; int w=log2(now); bitset<25> nowt(t); nowt&=bit[w]; f[s]=f[t]+nowt.count(); assert(f[s]<=m); cnt[f[s]]++; } cnt[f[0]]++; for(int i=0;i<=m;i++) printf("%d ",cnt[i]);puts(""); // dd d2=clock(); // printf("%lf\n",d2-d1); return 0; }
T2
有一定技巧性,屬於那種沒見過做不出來的題。真的非常巧妙。
首先是一個莫比烏斯反演轉化,利用莫比烏斯反演,每個詢問可以做成這樣:\(\sum_{d|x}\mu(d)\sum_{i=l}^r[d|a_i]\)。
我們考慮維護後面這個東西,不難發現可以把詢問分成兩塊,這樣我們所有的詢問都變成了這樣:\(\sum_{d|x}\mu(d)\sum_{i=1}^r[d|a_i]\)。
然後我們考慮做上面這個東西。首先我們可以把詢問離線下來,然後按照 \(r\) 排序。
然後我們用雙指標去做這個東西,當可以回答詢問的時候,我們就回答詢問。然後沒遇到一個 \(a\) 或者是查詢一個 \(x\),我們用 \(\sqrt n\)
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 100010 #define M number using namespace std; const int INF=0x3f3f3f3f; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } template<typename T> inline T Max(T a,T b){return a<b?b:a;} int Prime[N],tail,Mu[N],n,a[N],Q,qcnt,ans[N],c[N]; bool NotPrime[N]; struct Ques{ int r,x,type,id; inline Ques(){} inline Ques(int r,int x,int type,int id) : r(r),x(x),type(type),id(id) {} inline bool operator < (const Ques &b)const{return r<b.r;} }ques[N<<1]; inline void GetMu(int n){ NotPrime[1]=1;Mu[1]=1; for(int i=2;i<=n;i++){ if(!NotPrime[i]) Prime[++tail]=i,Mu[i]=-1; for(int j=1;j<=tail&&Prime[j]*i<=n;j++){ NotPrime[i*Prime[j]]=1; if(i%Prime[j]==0) break; else Mu[i*Prime[j]]=-Mu[i]; } } } inline void Init(){ read(n);read(Q); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=Q;i++){ int l,r,x;read(l);read(r);read(x); if(l-1>0) ques[++qcnt]=Ques(l-1,x,-1,i); ques[++qcnt]=Ques(r,x,1,i); } sort(ques+1,ques+qcnt+1); } inline void Insert(int x){ int i;for(i=1;i*i<x;i++) if(x%i==0){c[i]++;c[x/i]++;}if(i*i==x) c[i]++; } inline int Query(int x){ int i,nowans=0; for(i=1;i*i<x;i++) if(x%i==0){nowans+=Mu[i]*c[i];nowans+=Mu[x/i]*c[x/i];}if(i*i==x) nowans+=Mu[i]*c[i]; return nowans; } inline void Solve(){ for(int i=1,j=0;i<=n;i++){ Insert(a[i]); while(j+1<=qcnt&&ques[j+1].r<=i){j++;ans[ques[j].id]+=Query(ques[j].x)*ques[j].type;} } for(int i=1;i<=Q;i++) printf("%d\n",ans[i]); } int main(){ // freopen("my.in","r",stdin); // freopen("my.out","w",stdout); GetMu(100000); Init();Solve(); }
T3
這個題已經把暴力想出來了,但是放棄了進一步優化,這是我的問題,當時在 BC 之間抉擇的時候,應該優先選擇優化 dp 的。這樣起碼還有些盼頭。
暴力 dp 很好想,設 \(f_{l,r,k}\) 表示區間 \(l,r\) 內 \(k\) 是否有可能贏。那麼我們在左邊列舉一個能夠被 \(k\) 打敗的數 \(a\),右邊列舉一個能被 \(k\) 打敗的數 \(b\),然後轉移就可以。
稍微想一下可以列舉到 \(n^4\),就是我們關注的是左邊有沒有,右邊有沒有,所以不用列舉數對。
然後我們考慮如何優化到 \(n^3\),考慮第 \(3\) 維實際上並沒有什麼用處,這是因為:\(f_{l,r,k}=f_{l,k,k}\and f_{k,r,k}\)。然後我們直接可以省掉第 \(3\) 維,把 dp 變成 \(f_{l,r,0/1}\) 其中 \(0\) 表示原來的 \(f_{l,r,l}\) ,\(1\) 表示原來的 \(f_{l,r,r}\)。
因為 dp 值為 bool,所以我們考慮用 bitset 優化,具體來說,就是用 bit1[l]
來存 \(f_{l,k,0}\),用 bit2[r]
來存 \(f_{k,r,1}\),然後因為需要保證能夠打敗,所以我們轉移的時候在 and 上一個 beat[k]
表示能被 \(k\) 打敗的集合即可。
注意我們最好做完一階段 dp,在去更新兩個幫助轉移的 bitset。
程式碼:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 2010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
bitset<N> bit1[N],bit2[N],Beat[N];
int f[N][N][2],n;
char s[N];
inline void Init(){
read(n);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=n;j++) if(s[j]=='1') Beat[i][j]=1;
}
}
inline void Dp(){
for(int i=1;i<=n;i++){
f[i][i][0]=f[i][i][1]=1;
bit1[i][i]=bit2[i][i]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=n-i+1;j++){
int l=j,r=j+i-1;
f[l][r][0]=(Beat[l]&bit1[l+1]&bit2[r]).any();
f[l][r][1]=(Beat[r]&bit1[l]&bit2[r-1]).any();
// printf("f[%d][%d][0]=%d f[%d][%d][1]=%d\n",l,r,f[l][r][0],l,r,f[l][r][1]);
}
for(int j=1;j<=n-i+1;j++){
int l=j,r=j+i-1;
if(f[l][r][0]) bit2[r][l]=1;
if(f[l][r][1]) bit1[l][r]=1;
}
}
for(int i=1;i<=n;i++){
if(f[1][i][1]&&f[i][n][0]) printf("%d ",i);
}
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
Init();Dp();
return 0;
}
T4
非常巧妙的一個轉化。首先我們考慮這個題並不是簡單的,邊不在樹邊上,我們就可以隨便走的題目,這是因為我們題目中的主人公不具有預見性,這個題目實際上是一個博弈模型,想像和你做博弈的人,每當你走到一個點時,就會考慮是否刪掉相鄰的一個邊,且其刪邊的機會只有一次,他的目標是把你的路徑權值和最大化,你的目標是最小化權值和。
以下設 \(s\) 為最短路樹根節點。
我們考慮設 \(val_i\) 表示在最短路樹上去掉 \(i\) 與其父親的邊之後走到根的最短路。\(f_i\) 表示節點 \(i\) 的答案,那麼我們考慮如何計算。
顯然,如果對手打算在我們走到這個節點的時候刪邊,那麼其一定刪最短路上與 \(i\) 相連的父邊,這樣答案就是 \(val_i\),否則,我們可以走到某個節點 \(j\),然後這個時候狀態變成了 \(f_j\),所以我們有轉移 \(f_i=\max(val_i,\min\limits_{(j,i)\in E}(f_j+w(i,j))\)。
現在我們有兩個問題需要解決:
- 如何計算 \(val\)。
- 以什麼順序轉移 \(f\)。
我們首先考慮第二個問題。
首先 \(f_s=0\),其次,不難發現,如果 \(f_i\) 最終選擇從 \(f_j\) 轉移過來,那麼一定有 \(f_i>f_j\),如果從 \(val_i\) 轉移過來,那麼一定要麼節點 \(i\) 沒有任何出邊,要麼 \(val_i\) 最大。
上述性質標明,這個轉移一定沒有環,換句話說,我們一定能夠找到一個轉移方式。
不難發現,如果我們每次找當前 \(f\) 值最小的還沒有被擴充套件的節點,那麼一定不存在其餘節點能夠更新它。
所以我們可以通過迪傑斯特拉的方式來更新,因為滿足貪心性質,所以正確性顯然。
然後我們考慮如何計算 \(val_x\)。
首先一個性質是它一定只走了一條非樹邊,如果走了兩條的話,不滿足最短路樹的性質,這個容易證明。且這個非樹邊的兩個節點在樹上之間的路徑一定經過這個節點 \(x\),這個畫圖不難證明,否則的話不滿足是一棵樹。當然前提得是走這個邊是最優邊。
我們發現這個最優邊一定是從 \(x\) 往深走,走到某個節點,然後通過一條非樹邊,走到另一顆子樹,然後走到根節點。
我們考慮設 \((a,b,w)\) 為這個非樹邊。那麼 \(val_x=dis_a+dis_b+w+dis_x\),其中 \(dis\) 為最短路樹上節點到根的距離。
暴力的想法是列舉這個最右邊,不過這樣複雜度爆炸。
我們不如把所有的非樹邊拿出來,按照 \(dis_a+dis_b+w\) 進行排序,然後對於一個非樹邊 \((a,b,w)\),覆蓋這兩個點到 \(lca(a,b)\) 的所有點的 \(val\),當然不覆蓋 \(lca\),我們用並查集維護覆蓋的點,具體來說,當一個點被覆蓋,我們就把它和它的父親合併,這樣我們查詢的時候會直接跳到其父親。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 2010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
bitset<N> bit1[N],bit2[N],Beat[N];
int f[N][N][2],n;
char s[N];
inline void Init(){
read(n);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=n;j++) if(s[j]=='1') Beat[i][j]=1;
}
}
inline void Dp(){
for(int i=1;i<=n;i++){
f[i][i][0]=f[i][i][1]=1;
bit1[i][i]=bit2[i][i]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=n-i+1;j++){
int l=j,r=j+i-1;
f[l][r][0]=(Beat[l]&bit1[l+1]&bit2[r]).any();
f[l][r][1]=(Beat[r]&bit1[l]&bit2[r-1]).any();
// printf("f[%d][%d][0]=%d f[%d][%d][1]=%d\n",l,r,f[l][r][0],l,r,f[l][r][1]);
}
for(int j=1;j<=n-i+1;j++){
int l=j,r=j+i-1;
if(f[l][r][0]) bit2[r][l]=1;
if(f[l][r][1]) bit1[l][r]=1;
}
}
for(int i=1;i<=n;i++){
if(f[1][i][1]&&f[i][n][0]) printf("%d ",i);
}
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
Init();Dp();
return 0;
}