1. 程式人生 > 實用技巧 >補題:2020牛客暑期多校訓練營(第二場)

補題:2020牛客暑期多校訓練營(第二場)

Contest Link

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>
#include 
<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; }
View Code

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