補題:2020牛客暑期多校訓練營(第二場)
Solved:7/11
Upsolved:11/11
A. All with Pairs (牛客 5667A)
首先,所有字首/字尾的總數為$\sum |s_i|$級別的,所以不妨把所有後綴出現的個數先hash後用unordered_map統計一遍。
此時對於每個串的字首,若與某些字尾相等,那麼就能產生 字尾出現次數$\times$字首長度的平方 的貢獻。不過這樣會產生重複:如果一個字首為abcab,且有一定數量與之相等的字尾,那麼一定存在不少於這個數目的、等於ab的字尾(畢竟ab就是abcab的字尾)。但是在我們的統計中,在abcab中會計算一次貢獻,在ab中又會計算一次貢獻,而後者是我們不希望看到的。
能夠發現,所有會產生多餘貢獻的字首,一定是一個更長的字首在KMP中的nxt。於是考慮對每個字串$s_i$求一次nxt陣列,對於第$i$位($i$從$0$到$n-1$),能夠產生$cnt[s_1...s_i]\cdot (i+1)^2$的貢獻,並且如果$nxt[i]\neq -1$,就需要減去$cnt[s_1...s_i]\cdot (nxt[i]+1)^2$的重複貢獻。簡單驗證能夠發現這樣確實能將重複貢獻全部去除。
用unsigned long long自然溢位來hash真香。
#include <cstring> #include <iostream> #includeView Code<algorithm> #include <unordered_map> using namespace std; typedef unsigned long long ull; const int N=1000005; const int mod=998244353; int n; string s[N]; ull h[N]; int fail[N]; unordered_map<ull,int> cnt; int main() { ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++) { cin>>s[i]; ull tmp=0,base=1; for(int j=s[i].length()-1;j>=0;j--) { tmp=tmp+(s[i][j]-'a'+1)*base; ++cnt[tmp]; base=base*2333; } } long long ans=0; for(int i=1;i<=n;i++) { int p=-1; fail[0]=-1; for(int j=1;j<s[i].length();j++) { while(p!=-1 && s[i][p+1]!=s[i][j]) p=fail[p]; if(s[i][p+1]==s[i][j]) ++p; fail[j]=p; } ull tmp=0; for(int j=0;j<s[i].length();j++) h[j]=tmp=tmp*2333+s[i][j]-'a'+1; for(int j=s[i].length()-1;j>=0;j--) { ans=(ans+1LL*cnt[h[j]]*(j+1)%mod*(j+1))%mod; if(fail[j]!=-1) ans=(ans-1LL*cnt[h[j]]*(fail[j]+1)%mod*(fail[j]+1)%mod+mod)%mod; } } cout<<ans<<'\n'; return 0; }
B.Boundary (牛客 5667B)
rls用圓反演做的,變成了過原點直線通過的點數。
不過題解中的做法也很方便,利用了同一弦的圓周角相同的性質。先列舉一個點$i$,與原點相連就可以得到一條弦;再列舉一個點,看弦的圓周角的眾數是多少即可。為了避免將弦兩側的圓周角都統計進去,可以考慮使用向量叉積、要求叉積必須小於/大於$0$。那麼圓周角可以用點積除以叉積表示,即$\frac{1}{tan\theta}$。
寫的時候用了分數類。不過這題中只有一次除法,用浮點也應該無所謂。
#include <map> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; struct Point { int x,y; Point(int a=0,int b=0) { x=a,y=b; } }; inline Point operator -(const Point &X,const Point &Y) { return Point(X.x-Y.x,X.y-Y.y); } struct frac { ll x,y; frac(ll a=0,ll b=1) { ll gcd=__gcd(a,b); x=a/gcd,y=b/gcd; } }; inline bool operator <(const frac &X,const frac &Y) { return X.x*Y.y<Y.x*X.y; } const int N=2005; inline int mul(const Point &X,const Point &Y) { return X.x*Y.x+X.y*Y.y; } inline int cross(const Point &X,const Point &Y) { return X.x*Y.y-X.y*Y.x; } int n; Point p[N]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y); int ans=0; for(int i=1;i<=n;i++) { int mx=0; map<frac,int> mp; for(int j=1;j<=n;j++) { int val=cross(p[j],p[j]-p[i]); if(val<=0) continue; frac res(mul(p[j],p[j]-p[i]),cross(p[j],p[j]-p[i])); mx=max(++mp[res],mx); } ans=max(ans,mx+1); } printf("%d\n",ans); return 0; }View Code
C. Cover the Tree (牛客 5667C)
這題有很多亂搞的策略。首先可以目測出來,答案為總葉子數除以二。
在現場我們的做法是,先用類似找樹的重心的方法找出樹上的一個節點,使得其所有兒子的子樹內最大的葉子數最小。然後將葉子數最大的子樹拿出來,此時其葉子數$maxl$一定不超過總數的$\frac{1}{2}$。在其餘子樹的葉子中留出$maxl$個,然後將其他的葉子與不在同一子樹內的進行兩兩配對(這樣的方案一定存在,因為剩餘子樹中最大葉子數也不超過$maxl$),留出的$maxl$個與最大子樹內的進行配對。如果最後還留下一個(總葉子數可能為奇數),就與根相連即可。
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; typedef pair<int,int> pii; const int N=200005; int n; vector<int> v[N]; int root; int sz[N],mx[N]; inline void find(int x,int fa,int tot) { if((int)v[x].size()==1) sz[x]=1; mx[x]=0; for(int i=0;i<v[x].size();i++) { int nxt=v[x][i]; if(nxt==fa) continue; find(nxt,x,tot); sz[x]+=sz[nxt]; mx[x]=max(mx[x],sz[nxt]); } mx[x]=max(mx[x],tot-sz[x]); if(!root || mx[x]<mx[root]) root=x; } vector<int> pool[N]; void dfs(int x,int f,int id) { if((int)v[x].size()==1) pool[id].push_back(x); for(int i=0;i<v[x].size();i++) { int y=v[x][i]; if(y!=f) dfs(y,x,id); } } pii ord[N]; int main() { scanf("%d",&n); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); v[x].push_back(y); v[y].push_back(x); } int leaf=0; for(int i=1;i<=n;i++) if((int)v[i].size()==1) leaf++; printf("%d\n",(leaf+1)/2); find(1,0,leaf); int m=(int)v[root].size(); for(int i=0;i<m;i++) dfs(v[root][i],root,i),ord[i]=pii((int)pool[i].size(),i); sort(ord,ord+m); vector<pii> ans; int l=0,r=m-2,maxl=ord[m-1].first; while((leaf-maxl)>maxl) { ans.push_back(pii(pool[ord[l].second].back(),pool[ord[r].second].back())); pool[ord[l].second].pop_back(); pool[ord[r].second].pop_back(); if((int)pool[ord[l].second].size()==0) l++; if((int)pool[ord[r].second].size()==0) r--; leaf-=2; } while(l<=r) { ans.push_back(pii(pool[ord[l].second].back(),pool[ord[m-1].second].back())); pool[ord[l].second].pop_back(); pool[ord[m-1].second].pop_back(); if((int)pool[ord[l].second].size()==0) l++; } if((int)pool[ord[m-1].second].size()>0) ans.push_back(pii(root,pool[ord[m-1].second][0])); for(int i=0;i<ans.size();i++) printf("%d %d\n",ans[i].first,ans[i].second); return 0; }View Code
D. Duration (牛客 5667D)
簽到。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; int main() { int h,m,s; scanf("%d:%d:%d",&h,&m,&s); ll t1=h*3600+m*60+s; scanf("%d:%d:%d",&h,&m,&s); ll t2=h*3600+m*60+s; printf("%lld\n",abs(t2-t1)); return 0; }View Code
E. Exclusive OR (牛客 5667E)
看到xor的題目,第一反應是線性基。由於$A_i<2^{18}$,故線性基的秩也不超過$18$,也就是說如果我們選擇了不少於$18$個數,一定能取到最大值。
不過對於$i<18$的答案並沒有辦法用線性基求解。直到rls提了一句FWT才發現已經快忘了有這個東西了...直接把所有數用FWT捲起來就行了,卷$i$次就相當於選了$i$個數,IFWT以後看哪一位不為$0$就說明這個數能被選出來。為了防止溢位,可以在卷一次以後將所有大於$0$的數全部改成$1$。
不過這樣交上去以後WA了。要考慮一些特殊情況:假如$A_i$均為$1$,那麼選奇數個時必為$1$,選偶數個時必為$0$。這說明答案還和奇偶性相關,所以需要FWT一直捲到$i=20$,之後的答案就按照奇偶性等於$i=19,i=20$的了。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N=200005; void FWTxor(ll *a,int n) { for(int i=n;i>=2;i>>=1) { int m=i>>1; for(int j=0;j<n;j+=i) for(int k=0;k<m;k++) { ll x=a[j+k],y=a[j+m+k]; a[j+k]=x+y; a[j+m+k]=x-y; } } } void IFWTxor(ll *a,int n) { for(int i=n;i>=2;i>>=1) { int m=i>>1; for(int j=0;j<n;j+=i) for(int k=0;k<m;k++) { ll x=a[j+k],y=a[j+m+k]; a[j+k]=(x+y)/2; a[j+m+k]=(x-y)/2; } } } int n; int a[N],ans[N]; ll b[1<<19],tmp[1<<19]; int main() { scanf("%d",&n); int mx=0; for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=1,mx=max(mx,a[i]); int m=1; while(m<=mx) m<<=1; tmp[0]=1; FWTxor(b,m); FWTxor(tmp,m); for(int i=1;i<=min(n,20);i++) { for(int j=0;j<m;j++) tmp[j]=tmp[j]*b[j]; IFWTxor(tmp,m); for(int j=m-1;j>=0;j--) if(tmp[j]>0) { tmp[j]=1; ans[i]=max(ans[i],j); } FWTxor(tmp,m); printf("%d ",ans[i]); } for(int i=21;i<=n;i++) printf("%d ",ans[20-i%2]); return 0; }View Code
F. Fake Maxpooling (牛客 5667F)
其實對$nm$個數求gcd還挺危險的,不過現場貌似正好能過。倒是卡了空間,所以需要將一個數組複用。
題解給的做法是,用類似埃氏篩的方法,找到互質的兩個數,然後成倍增長。由於每對數只會被選到一次,所以是$O(nm)$的。
接著就是每個$k\times k$矩陣中的最大值了,可以用兩次單調佇列處理。第一次,求出每一行中,每$k$個數中的最大值;第二次,求出每一列中,之前求出的行中最大值的最大值。維護一個單調佇列,其中存的是下標,越靠隊頭數越小、越靠隊尾數越大,這樣就可以方便地在隊尾到當前位置大於$k$時彈出隊尾了。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N=5005; int n,m,sz; int gcd[N][N],a[N][N]; int head,rear,q[N]; int main() { scanf("%d%d%d",&n,&m,&sz); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(!gcd[i][j]) for(int k=1;k*i<=n && k*j<=m;k++) gcd[i*k][j*k]=k,a[i*k][j*k]=i*j*k; for(int i=1;i<=n;i++) { head=1,rear=0; for(int j=1;j<=m;j++) { while(head<=rear && a[i][j]>=a[i][q[rear]]) rear--; q[++rear]=j; gcd[i][j]=a[i][q[head]]; if(j>=sz && j-sz+1==q[head]) head++; } } ll ans=0; for(int i=sz;i<=m;i++) { head=1,rear=0; for(int j=1;j<=n;j++) { while(head<=rear && gcd[j][i]>=gcd[q[rear]][i]) rear--; q[++rear]=j; if(j>=sz) { ans+=gcd[q[head]][i]; if(j-sz+1==q[head]) head++; } } } printf("%lld\n",ans); return 0; }View Code
G. Greater and Greater (牛客 5667G)
這題涉及到的是一個Shift And的思想,之後還得好好學一學。
具體的做法是這樣的:首先可以對於$a$陣列中的每一個元素求一個bitset $s$,其中$s_i[j],1\leq j\leq m$表示$a[i]$是否大於$b[j]$(即將$a[i]$與$b$陣列整個比一遍)。這樣看似需要求$n$個bitset,但是實際上只有$m$級別的不同的bitset,因為一個數只可能比$b$中的$0,1,...,m$個數大。所以只需要對$b$求$m$個bitset,$a[i]$對應的bitset在排序後的$b$陣列中二分即可。
接著考慮用bitset $cur[i]$表示$a$中子段$a[i...i+m-1]$與$b$的大小關係。其中$cur[i][j]$表示,是否有$\forall k\in [j,m],a[i+(k-j)]\geq b[k]$($i$向右長度為$m-j+1$的子段是否依次大於等於$b$中的等長字尾),也就是說$i-j+1$是否為一個合法的起點(僅考慮$a[i...m-j+1]$時)。知道這個的用處在於,我們可以向後推一位。假如$a[i-1]\geq b[j-1]$,那麼我們就知道了$i-1$向右長度為$m-j+2$的子段也依次大於等於$b$中的等長字尾,即$cur[i-1][j-1]=1$。
於是$cur$可以這樣轉移:$cur[i]=((cur[i+1]\text{>>}1) | (1\text{<<}m)) \text{&} s[i]$,若$cur[i][1]=1$那麼就會對答案有$1$的貢獻。這樣理解會直觀一點:$s[i]$表示$a[i]$能大於等於$b$中哪些位,於是就能排除掉一些起點$j\leq i$。同時$cur[i+1]$中已經排除了一些起點$j$,所以兩者的and就是當前可能的起點。
也有更省空間的做法,不用存下$m$個bitset,而是將$a,b$排序後一起處理。
#include <cstdio> #include <bitset> #include <vector> #include <cstring> #include <algorithm> using namespace std; typedef pair<int,int> pii; const int N=150005; const int M=40005; int n,m; int a[N],b[M]; vector<pii> v; int pos[N]; bitset<M> s[M]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) scanf("%d",&b[i]),v.push_back(pii(b[i],i)); v.push_back(pii(0,0)); sort(v.begin(),v.end()); for(int i=1;i<v.size();) { s[i]=s[i-1]; int j=i; while(j<v.size() && v[j].first==v[i].first) { s[i].set(v[j].second); j++; } for(int k=i+1;k<j;k++) s[k]=s[i]; i=j; } for(int i=1;i<=n;i++) { int p=lower_bound(v.begin(),v.end(),pii(a[i],0))-v.begin(); if(p==v.size() || v[p].first>a[i]) p--; pos[i]=p; } int ans=0; bitset<M> cur,Im; Im.set(m); for(int i=n;i>=1;i--) { cur=((cur>>1)|Im)&s[pos[i]]; if(cur.test(1)) ans++; } printf("%d\n",ans); return 0; }View Code
H. Happy Triangle (牛客 5667H)
集合$MS$中的數$a,b$(不妨認為$a\leq b$)能與$x$構成三角形僅有以下幾種情況:
1. $a\leq b\leq x$,此時若有$a+b>x$即可。
2. $a\leq x\leq b$,此時若有$a+x>b$即可。
3. $x\leq a\leq b$,此時若有$a+x>b$即可。
2、3的條件雖然相同,但是由於$a,b,x$之間的大小關係不同,仍需要分開處理。1很簡單,從$MS$中取出兩個小於$x$、且最接近$x$的數。2也差不多,從$MS$中取出一個大於$x$、一個小於$x$的最接近$x$的數。3判斷起來就比較麻煩了,需要找出最小的$b-a$,且滿足$a\geq x$。
這其實可以通過離散化+線段樹來維護。顯然最小的$b-a$一定產生在相鄰兩數之間,所以我們需要維護的是相鄰兩數之差的最小值(這個差被放在較小數的位置上)。這樣查詢的時候$query(x,sz)$即可,稍微麻煩點的是插入、刪除。往$a,b$中插入一個$x$時,本來在$a$處的$b-a$被抹去了,而變成了$a$處的$x-a$和$x$處的$b-x$。而刪除$a,x,b$中的$x$時,本來在$a$處的$x-a$、在$x$處的$b-x$被抹去了,而變成了$a$處的$b-a$。不過需要注意出現重複數字時的判斷,如果$a$有重複,那麼在插入時$a$的值為$0$不變,在刪除時有可能從$0$變成$b-a$,用multiset中的count函式判斷一下就好了。
一個小技巧,就是可以在multiset中預先插入$-1$和$\infty$,相當於給定了左邊界和右邊界,可以省去不少繁瑣的討論。
#include <set> #include <vector> #include <cstdio> #include <algorithm> using namespace std; const int N=200005; const int INF=1<<30; int sz=1,t[N<<2]; inline void modify(int k,int x) { k=k+sz-1; t[k]=x; k>>=1; while(k) { t[k]=min(t[k<<1],t[k<<1|1]); k>>=1; } } inline int query(int k,int l,int r,int a,int b) { if(a>r || b<l) return INF; if(a>=l && b<=r) return t[k]; int mid=(a+b)>>1; return min(query(k<<1,l,r,a,mid),query(k<<1|1,l,r,mid+1,b)); } int n; int opt[N],val[N]; multiset<int> s; vector<int> v; inline int pos(int x) { return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&opt[i],&val[i]),v.push_back(val[i]); while(sz<n) sz<<=1; for(int i=1;i<(sz<<1);i++) t[i]=INF; sort(v.begin(),v.end()); v.resize(unique(v.begin(),v.end())-v.begin()); s.insert(-1),s.insert(INF); for(int i=1;i<=n;i++) { int prev=*(--s.lower_bound(val[i])); int next=*(s.lower_bound(val[i])); if(opt[i]==1) { s.insert(val[i]); if(prev!=-1 && s.count(prev)==1) modify(pos(prev),val[i]-prev); if(next!=INF) modify(pos(val[i]),next-val[i]); } if(opt[i]==2) { s.erase(s.lower_bound(val[i])); next=*s.upper_bound(val[i]); if(s.count(val[i])==0) { modify(pos(val[i]),INF); if(prev!=-1 && s.count(prev)==1) modify(pos(prev),next==INF?INF:next-prev); } if(s.count(val[i])==1) modify(pos(val[i]),next==INF?INF:next-val[i]); } if(opt[i]==3) { int flag=0; if(prev!=-1) { int pprev=*(--(--s.lower_bound(val[i]))); if(pprev!=-1 && prev+pprev>val[i]) flag=1; } if(prev!=-1 && next!=INF && prev+val[i]>next) flag=2; if(query(1,pos(val[i]),sz,1,sz)<val[i]) flag=3; printf(flag?"Yes\n":"No\n"); } } return 0; }View Code
I. Interval (牛客 5667I)
將這個問題向網格圖上轉化是很自然的思路。原題目中的區間$[l,r]$就相當於網格上的$(l,r)$,原題目中$\{l,r,L\}$的限制就相當於$(l,r)$和$(l+1,r)$間的邊不能走,$\{l,r,R\}$的限制同理。
之後需要有網路流的直覺(我又沒有...)。這題可以轉化成一個最大流模型:源點是$(1,n)$,匯點自己建,如果兩點之間有$\{l,r,L/R,c\}$的限制則流量為$c$,否則流量為$\infty$,所有$(i,i)$都向匯點連流量為$\infty$的邊。此時的最大流就是題目中的答案。為什麼最大流求出的會是題目中的“最小代價”呢?因為假設$(1,n)$外的限制有很多層,每一層的代價之和互不相同,那麼從$(1,n)$流到$(i,i)$的最大流量,取決於代價之和最小(即總流量最小)的那一層。
不過這樣還不算完。當前的圖是$n^2$點$n^2$邊的,直接跑Dicnic肯定吃不消,還需要進行一個奇妙的轉化。
可以參考:MaxMercer - 對偶圖對於平面圖最小割的求解(網路流問題),得到的結論是平面圖的最小割等於對偶圖的最短路。本題中的網格圖顯然是平面圖,那麼考慮怎麼構造對偶圖。
首先將源點與匯點連一條線(圖中藍線),這條線所新圍成的面對應的對偶圖中點為$S'$,最外面的面對應的對偶圖中點為$T'$。對偶圖中$S'$到$T'$的邊長度為$0$,其餘的邊等於穿過的原圖中邊的長度。
不過這個圖比較特殊,實際上原圖大概長下圖這樣:
最後在對偶圖上去掉$S'-T'$邊後,從$S'$到$T'$跑一個最短路就是答案了。
需要注意的是,最短路的長度可能超過$1\times 10^9$,所以不妨在求最短路時直接就不走邊權為$\infty$的邊。
#include <queue> #include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; typedef pair<ll,int> pli; typedef pair<int,int> pii; const int N=505; const int oo=1<<30; const ll OO=1LL<<60; int n,m; int e[N][N][2]; vector<pii> v[N*N]; inline void add(int x,int y,int w) { v[x].push_back(pii(y,w)); v[y].push_back(pii(x,w)); } ll d[N*N]; priority_queue<pli,vector<pli>,greater<pli> > Q; int main() { for(int i=1;i<N;i++) for(int j=1;j<N;j++) for(int k=0;k<2;k++) e[i][j][k]=oo; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { char dir; int x,y,c; scanf("%d%d",&x,&y); dir=getchar(); while(dir!='L' && dir!='R') dir=getchar(); scanf("%d",&c); if(dir=='L') e[x][y][0]=c; //vertical else e[x][y-1][1]=c; //horizontal } int s=(n-1)*(n-1)+1,t=s+1; for(int i=1;i<n;i++) { for(int j=i;j<n-1;j++) { int id=(i-1)*(n-1)+j,w=e[i][j+1][0]; add(id,id+1,w); } add(i*(n-1),s,e[i][n][0]); } for(int i=1;i<n;i++) { add(i,t,e[1][i][1]); for(int j=1;j<i;j++) { int id=(j-1)*(n-1)+i,w=e[j+1][i][1]; add(id,id+n-1,w); } } for(int i=1;i<=t;i++) d[i]=OO; d[s]=0; Q.push(pli(0,s)); while(!Q.empty()) { ll D=Q.top().first; int x=Q.top().second; Q.pop(); if(D>d[x]) continue; for(int i=0;i<v[x].size();i++) { int y=v[x][i].first,w=v[x][i].second; if(w<oo && D+w<d[y]) { d[y]=D+w; Q.push(pli(d[y],y)); } } } printf("%lld\n",d[t]==OO?-1:d[t]); return 0; }View Code
J. Just Shuffle (牛客 5667J)
由於保證$k$是一個很大的質數,那麼任意迴圈節長度$len$都會與$k$互質,這可以保證對於$A$找出的迴圈節就是$P$中的迴圈節(只不過排列不同)。
於是就需要考慮如何對每個環分別構造了。
考慮假設$P=\{2,3,4,5,6,7,1\}$,進行$3$次置換後會得到$A=\{4,5,6,7,1,2,3\}$。這時,對$A$從$1$開始找環,能夠得到$A'=\{4,7,3,6,2,5,1\}$。可以看出,$P[-1]=A'[len-1]=1$,$P[0]=A'[(6+5)mod\ 7]=2$,$P[2]=A'[(6+5\times 2)mod\ 7]=3$,$P[3]=A'(6+5\times 3)mod\ 7]=4$,也就是說$P$可以通過在$A'$上隔著相等距離$L=5$選數而得到。於是從$1$迴圈到$len$求合法的間隔距離即可,若出現多個則不存在合法方案。
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int N=100005; int n,k; int a[N]; bool vis[N]; int ans[N]; int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); bool flag=true; for(int i=1;i<=n;i++) { if(vis[i]) continue; vector<int> v; int p=a[i]; while(!vis[p]) { v.push_back(p),vis[p]=true; p=a[p]; } int L=-1,len=v.size(); for(int i=1;i<=len;i++) if(1LL*(k%len)*i%len==1) { if(L!=-1) flag=false; L=i; } int cur=len-1; for(int i=0;i<len;i++) { int nxt=(cur+L)%len; ans[v[cur]]=v[nxt],cur=nxt; } } if(!flag) printf("-1\n"); else for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' '); return 0; }View Code
K. Keyboard Free (牛客 5667K)
卡了半天精度,結果發現是算的有問題...
這題直接算是比較困難的,可以考慮先列舉、再積分。
不妨讓A點一直處於$(0,r_A)$,那麼根據對稱性,我們只需要在$x<0$的半圓弧上取$B$點。此時將AB邊作為三角形的底邊,那麼我們需要計算在半徑為$r_C$的圓周上取C點時,到AB邊的高的期望。
首先可以求出原點到AB所在直線的距離$d$,利用$d=\overrightarrow{OA}\times \overrightarrow{OB}\cdot \frac{1}{|AB|}$即可。那麼半圓心角$\alpha=acos(\frac{d}{r})$。
不妨只計算$\theta\in [0,\pi]$的情況,因為另一半隻是簡單對稱。當$\theta\in [0,\alpha]$時,高為$r(cos\theta-cos\alpha)$;當$\theta\in [\alpha,\pi]$時,高為$r(cos\alpha-cos\theta)$。於是對兩部分分別積分。
\[\int_{0}^{\alpha}r(cos\theta-cos\alpha)d\theta=r(sin\theta-\theta\cdot cos\alpha)|_0^{\alpha}=r(sin\alpha-\alpha cos\alpha)\]
\[\int_{\alpha}^{\pi}r(cos\alpha-cos\theta)d\theta=r(\theta\cdot cos\alpha-sin\theta)|_{\alpha}^{\pi}=r[(\pi-\alpha) cos\alpha-(sin\pi-sin\alpha)]\]
兩者合併起來就是$r[2sin\alpha+(\pi-2\alpha)cos\alpha]$。期望的話需要再除以$\pi$。
然後這裡有一個小坑點,因為B在取點時只取了半圓弧,所以半圓弧的兩端點均只產生$\frac{1}{2}$倍的貢獻。如果取的是整圓就沒這個問題了。
(long double運算好慢啊...如果不是很卡精度,用double就夠了)
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef double ld; const ld pi=acos(-1.0); const ld eps=0.0000001; struct Point { ld x,y; Point(ld a=0.0,ld b=0.0) { x=a,y=b; } }; inline Point operator -(Point X,Point Y) { return Point(X.x-Y.x,X.y-Y.y); } inline ld cross(Point X,Point Y) { return X.x*Y.y-X.y*Y.x; } inline ld pw2(ld x) { return x*x; } inline ld dist(Point X,Point Y) { return sqrt(pw2(X.x-Y.x)+pw2(X.y-Y.y)); } int r[3]; int main() { int T; scanf("%d",&T); while(T--) { for(int i=0;i<3;i++) scanf("%d",&r[i]); sort(r,r+3); double ans=0.0; Point A(0,r[0]); for(int i=0;i<=2000;i++) { ld angle=i*pi/2000.0; Point B(-r[1]*sin(angle),r[1]*cos(angle)); ld d; if(dist(A,B)>eps) d=abs(cross(A,B))/dist(A,B); else d=r[0]; ld a=acos(d/r[2]); ld res=0.0; res=r[2]*(sin(a)*2+cos(a)*(pi-a*2)); if(i==0 || i==2000) res/=2.0; ans=ans+res*dist(A,B); } printf("%.10f\n",double(ans/4000.0/pi)); } return 0; }View Code