終於把博客補全了/px
然而這場考得也好不到哪裏去,寫了四篇博客然而四場都是炸的。怪自閉的
說實在的這場至少分數不算低,還是可以接受的(誰要你自己接受啊
$T1$幾乎可以說是很套路的題了,但是花的時間還是有點長了
然後$T2$數據非常水導致我寫的隨機化過了子任務$1,9,10$而剩下的$PC$了
$T3$很早就想到了$O(n^3)$的做法,但是因爲數據很有梯度於是一直執着於卡常。
估分$64/68$那樣,本機跑$64$的數據是$0.26s$交上去還是被卡了,我人都傻了
然而菜就是菜,搞得好像多$4pts$對我有什麼影響一樣
這一輪的兩場下來,主要還是因爲$day1$炸的太厲害了,所以總分依舊滾的有點遠,貌似還是會被擠來擠去到退役線之外
教練說不要太看重模擬賽成績所以其實我也沒仔細研究。的確沒必要
模擬賽只是在爲以後各方面積累經驗而已,在某些方面有提升這就足夠了
T1:A
大意:$w\times h$的點陣,從中選取$n$個點在一條直線上的方案數。$w,h \le 10^5$
思路挺多的,比較可行的一個是枚舉起點和終點然後在線段上的點中選取$n-2$。因爲起點終點不同所以一定不重不漏。
然後發現其實我們只要枚舉終點-起點這個向量就足夠了。首先平行與座標軸的特判掉,剩下的就是斜向左下或右下的兩種。
等價的,只計數一種然後乘二就可以了。式子大約長這樣:
$\sum\limits_{i=1}^{w} \sum\limits_{j=1}^{h} \binom{gcd(i,j)-1}{n-2} (w-i+1)(h-j+1)$
$=\sum\limits_{g=1}^{max(w,h)} \binom{g-1}{n-2} \sum\limits_{i=1}^{\frac{w}{g}} (w-ig+1) \sum\limits_{j=1}^{\frac{h}{g}} (h-jg+1) [gcd(i,j)=1]$
$=\sum\limits_{g=1}^{max(w,h)} \binom{g-1}{n-2} \sum\limits_{i=1}^{\frac{w}{g}} \sum\limits_{j=1}^{\frac{h}{g}} \sum\limits_{d|i,d|j} \mu(d) (w-ig+1) (h-jg+1)$
$=\sum\limits_{g=1}^{max(w,h)} \binom{g-1}{n-2} \sum\limits_{d=1}^{\frac{max(w,h)}{g}} \mu(d) \sum\limits_{i=1}^{\frac{w}{gd}} \sum\limits_{j=1}^{\frac{h}{gd}}(w-igd+1) (h-jgd+1)$
$=\sum\limits_{gd=1}^{max(w,h)} \sum\limits_{g|dg} \binom{g-1}{n-2} \mu(\frac{gd}{g}) cal(w,gd) cal(h,gd)$
對於每種$gd$,$O(n\ ln\ n)$處理前半部分。後面的$cal$是$O(1)$的(等差數列求和),然後就可以直接做了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int $=1e5+5,mod=323232323; 4 int fac[$],inv[$],mu[$],np[$],p[$],pc,F[$],ans,w,h,n; 5 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 6 int C(int b,int t){return 1ll*fac[b]*inv[t]%mod*inv[b-t]%mod;} 7 int cal(int n,int d){int t=n/d;return (t*(n+1ll)-d*(t+1ll)*t/2)%mod;} 8 int main(){ 9 freopen("a.in","r",stdin); freopen("a.out","w",stdout); 10 for(int i=fac[0]=1;i<$;++i)fac[i]=fac[i-1]*1ll*i%mod; 11 inv[$-1]=qp(fac[$-1],mod-2); 12 for(int i=$-2;~i;--i)inv[i]=inv[i+1]*(i+1ll)%mod; 13 mu[1]=1; 14 for(int i=2;i<$;++i){ 15 if(!np[i])mu[i]=-1,p[++pc]=i; 16 for(int j=1,x;(x=i*p[j])<$;++j) 17 if(i%p[j])mu[x]=-mu[i],np[x]=1; 18 else{np[x]=1;break;} 19 } 20 scanf("%d%d%d",&w,&h,&n); if(w>h)swap(w,h); w--;h--; 21 if(n==1)return printf("%lld",(w+1ll)*(h+1)%mod),0; 22 for(int g=n-1;g<=h;++g)for(int x=g;x<=h;x+=g)F[x]=(F[x]+mu[x/g]*C(g-1,n-2))%mod; 23 for(int g=1;g<=h;++g)ans=(ans+F[g]*1ll*cal(w,g)%mod*cal(h,g))%mod;ans=ans*2%mod; 24 ans=(ans+(w+1ll)*C(h+1,n)+(h+1ll)*C(w+1,n))%mod; 25 printf("%d",(ans+mod)%mod); 26 }
T2:B
大意:長爲$n$的數軸上有$n$條線段,你要選擇儘量少的點使得每條線段上都有點。你要把每條線段分配給在線段上的一個點。
代價是線段中點到你所選的點的距離的二倍。最小化點數的基礎上最小化代價。$n \le 5 \times 10^5$
首先最小化點數還是比較簡單的:直接貪心,一直咕咕咕,能不放點就不放點,也就是線段按照右端點排序,如果當前左端點已經被覆蓋就沒事,否則就在右端點放一個。
最小化代價就比較困難了:考慮$dp$
首先有一個結論:按照剛纔貪心的過程按所有右端點把數軸砍成若干段。除了最後一段,每段中恰好選一個。
那麼$O(n^2)dp$就可以隨便寫了。然後還有一個結論:決策單調性。
用那種$solve(l,r,L,R)$做就可以了。時間複雜度$O(n\ log\ n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 const int $=1e6+5; 5 int n,mxl[$],p[$],ans; ll cnt[$],sum[$],dp[$],Ans=1e18; 6 struct L{int l,r;}_[$]; 7 ll cal(int l,int r){int m=l+r>>1;return mxl[r-1]>l?1e17:r*(cnt[r]-cnt[m])-(sum[r]-sum[m])+(sum[m]-sum[l])-l*(cnt[m]-cnt[l])+dp[l];} 8 void solve(int l,int r,int L,int R){ 9 int m=L+R>>1,P; if(R<L)return; 10 for(int i=l;i<=r;++i)if(cal(i,m)<dp[m])dp[m]=cal(P=i,m); 11 solve(l,P,L,m-1);solve(P,r,m+1,R); 12 } 13 int main(){ 14 freopen("b.in","r",stdin); freopen("b.out","w",stdout); 15 scanf("%d",&n); for(int i=1,l,r;i<=n;++i)scanf("%d%d",&l,&r),l<<=1,r<<=1,_[i]=(L){l,r},mxl[r]=max(mxl[r],l),cnt[l+r>>1]++,sum[l+r>>1]+=l+r>>1; 16 sort(_+1,_+1+n,[](L x,L y){return x.r<y.r;}); 17 for(int i=1;i<=n;++i)if(_[i].l>p[ans])p[++ans]=_[i].r; 18 n<<=1; memset(dp,63,sizeof dp); 19 for(int i=1;i<=n;++i)cnt[i]+=cnt[i-1],sum[i]+=sum[i-1],mxl[i]=max(mxl[i],mxl[i-1]); 20 for(int i=1;i<=p[1];++i)dp[i]=i*cnt[i]-sum[i]; 21 for(int i=2;i<=ans;++i)solve(p[i-2]+1,p[i-1],p[i-1]+1,p[i]); 22 for(int i=p[ans-1]+1;i<=n;++i)if(mxl[n]<=i)Ans=min(Ans,dp[i]+sum[n]-sum[i]-(cnt[n]-cnt[i])*i); 23 printf("%d %lld",ans,Ans); 24 }
T3:C
大意:求滿足$dfs$序爲$i$的點的子樹大小$\ge A_i$的樹的個數。$n \le 10^4$
首先子樹中$dfs$序是連續的,那麼如果有兩條限制$[i,i+A_i)$和$[j,j+A_j)$是相交的,如若$i<j<i+A_i<j+A_j$。
那麼對$i$的限制也可以調整爲$[i,j+A_j)$。也就是說相交關係可以轉化爲包含。那麼也就是說只剩下了不相交和包含兩種關係。可以建樹了。
具體的實現方法是:倒着掃一遍並且維護一個棧,每次嘗試與棧頂合併即可。
建出樹之後就是:一個點的所有兒子的確都是它的兒子,所有兒子之間不相交。
我們考慮合併兩棵樹並保證第一棵樹的$dfs$序嚴格小於第二個,我們發現,第二棵樹的根只能接在第一棵樹的最右側的一條鏈的最右側那些點上。
我們稱之爲右鏈。設$dp[i][j]$表示當前在處理$i$的限制區間,當前右鏈長度爲$j$。轉移沒啥問題。
鏈長的限制是子樹大小,所以時間複雜度是子樹合併。總時間複雜度是$O(n^2)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int $=10001,mod=323232323; 4 int ans,n,a[$],f[$][$],st[$],tp,sz[$],g[$]; vector<int>E[$]; 5 int mo(int x){return x>=mod?x-mod:x;} 6 void dfs(int p){ 7 f[p][0]=1; sz[p]=1; 8 for(int y:E[p]){ 9 dfs(y); 10 for(int i=sz[p]-1;~i;--i)f[p][i]=mo(f[p][i]+f[p][i+1]); 11 for(int i=0;i<sz[p];++i)for(int j=0;j<sz[y];++j)g[i+j+1]=(g[i+j+1]+1ll*f[p][i]*f[y][j])%mod; 12 sz[p]+=sz[y]; 13 for(int i=0;i<sz[p];++i)f[p][i]=g[i],g[i]=0; 14 } 15 } 16 int main(){ 17 freopen("c.in","r",stdin); freopen("c.out","w",stdout); 18 scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i]),a[i]=i+a[i]-1; a[1]=n; 19 for(int i=n;i;--i){ 20 while(tp&&st[tp]<=a[i])a[i]=max(a[i],a[st[tp]]),E[i].push_back(st[tp]),tp--; 21 st[++tp]=i; if(a[i]>n)return puts("0"),0; 22 }dfs(1);for(int i=0;i<n;++i)ans=mo(ans+f[1][i]); printf("%d",ans); 23 }