2019 ICPC 西安邀請賽

西安邀請賽重現


A.Tasks

簽到題,排序一下就行了


C.Angel 's Journey

題意:

給定rx、ry、r、x、y,
圓心(rx,ry),半徑爲r,終點(x,y),起點在圓的底部,如圖:
在這裏插入圖片描述
圓底部的紅色點是起點,
藍色部分是不能走的地方,也就是說過圓心的水平線以下和圓內是不能走的,圓弧可以走
保證終點(x,y)在白色範圍,問起點到終點的最小距離是多少

思路:

分類討論:
在這裏插入圖片描述
如果終點在1或者3,那麼答案就是四分之一週長加上棕色點到終點的距離。
如果終點在2,那麼答案就是四分之一週長加上終點到切點的距離,再加上一小段圓弧(畫下圖就明白了)

code:

#include<bits/stdc++.h>
using namespace std;
const double pi=acos(-1);
double rx,ry,r;//圓心以及半徑
double x,y;//終點
double cir;//周長
double ans;//答案
double getd(double x1,double y1,double x2,double y2){
    return sqrt(pow(x1-x2,2)+pow(y1-y2,2));
}
signed main(){
    int T;
    cin>>T;
    while(T--){
        cin>>rx>>ry>>r>>x>>y;
        cir=2*pi*r;
        if(x<=rx-r){//左邊
            ans=cir/4+getd(x,y,rx-r,ry);
        }else if(x>=rx+r){//右邊
            ans=cir/4+getd(x,y,rx+r,ry);
        }else{//上面
            double d=getd(x,y,rx,ry);//點到圓心的長度
            double dd=sqrt(d*d-r*r);//點到切點的長度
            double ddd=getd(x,y,x,ry);//點到水平直徑的長度
            double sin1=ddd/d;
            double sin2=dd/d;
            double angle=asin(sin1)-asin(sin2);//小角的角度的弧度制,用的時候要轉成角度
            ans=cir/4+dd+angle*180/pi/360*cir;
        }
        printf("%.4f\n",ans);
    }
    return 0;
}

D.Miku and Generals

題意:

給n個物品和m個關係,每個物品有權值c(i),保證權值是100的倍數
每個關係(a,b)表示a和b不能在同一組
現在要你將這n個物品分成兩份,使得兩份的權值和差值儘可能小,輸出兩份中的權值和較大者。
題目保證有解
數據範圍:T<=10組數據,n<=200,m<=200,c(i)<=5e4

思路:

因爲權值是100的倍數,因此可以在開始將所有權值除以100,最後輸出答案的時候乘上100就行了。

題目要求輸出兩份中的較大者,因爲只有兩份,所以計算出較小者,然後用總權值去減就能計算出較大者了。

因爲題目保證有解,因此m個關係中不會發生類似a-b,b-c,c-a這種奇環衝突,因此m個關係練成的圖中,每個連通塊都是一個二分圖。

跑二分圖染色把每一個連通塊變成二分圖,因爲同半部的必須選在一起,因此這個連通塊就變成了形如(x1,x2)的二元組,意思是這個物品要不選擇權值x1,要不選擇權值x2。對於孤立的點,二元組爲(x1,0)。

跑完二分圖染色之後物品就變成若干二元組了。現在問題就變成對於每個二元組,選擇它的一種權值,使得權值和最接近總權值的一半。

令d(i,j)爲前i個物品是否能組成權值爲j。第一維的大小爲200,第二爲開總權值的一半就行了,最大總權值爲5e4*200=1e7,但是可以除以100,且只需要開一半,因此只要開5e4就行了。

dp部分的複雜度最大200*5e4=5e6,滿足時限。

然後遍歷d(n,k)找出最接近總權值一半的k即可計算出答案。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=205;
vector<pair<int,int> >a;
vector<int>g[maxm];
int d[maxm][50000+5];
int sum1,sum2;
int mark[maxm];
int c[maxm];
int n,m;
void init(){
    a.clear();
    for(int i=1;i<=n;i++)g[i].clear();
    for(int i=1;i<=n;i++)mark[i]=0;
}
void dfs(int x,int color){
    mark[x]=1;
    if(color==1)sum1+=c[x];
    else sum2+=c[x];
    for(int v:g[x]){
        if(mark[v])continue;
        dfs(v,!color);
    }
}
signed main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        init();
        int tot=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&c[i]);
            c[i]/=100;
            tot+=c[i];
        }
        int all=tot;
        tot/=2;
        for(int i=1;i<=m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for(int i=1;i<=n;i++){
            if(!mark[i]){
                sum1=sum2=0;
                dfs(i,1);
                a.push_back({sum1,sum2});
            }
        }
        int len=a.size();
        for(int i=0;i<len;i++){
            for(int j=0;j<=tot;j++){
                d[i][j]=-1;
            }
        }
        d[0][a[0].first]=1;
        d[0][a[0].second]=1;
        for(int i=1;i<len;i++){
            for(int j=0;j<=tot;j++){
                if(d[i-1][j]!=-1){
                    if(j+a[i].first<=tot)d[i][j+a[i].first]=1;
                    if(j+a[i].second<=tot)d[i][j+a[i].second]=1;
                }
            }
        }
        int ans=-1;
        for(int i=tot;i>=0;i--){
            if(d[len-1][i]!=-1){
                ans=i;
                break;
            }
        }
        ans=all-ans;
        printf("%d\n",ans*100);
    }
    return 0;
}

J.And And And

題意:

給一顆n個節點的有根樹,根爲1,樹邊有邊權。
現在要求計算:
在這裏插入圖片描述
意思是對於某一路徑,路徑的貢獻是這條路徑中,子路徑異或和爲0的子路徑數量
要求輸出所有路徑的貢獻和

思路:

顯然不能按題目要求計算每條路徑的子路徑
應該計算每個異或和爲0的路徑的貢獻。

基本思路:
假設x到v的異或和爲0

1.如果v是x的兒子,則貢獻爲((n-sz(x)+1)+(sz(x)-sz(k)-1))*(sz(v)),k是x到v這條鏈上,x的第一個兒子,可以下面的圖。
這部分感覺挺難想的很完備,很容易少掉(sz(x)-sz(k)-1)這個部分(反正我是少了)

2.如果v不是x的兒子,則貢獻爲sz(x)*sz(v)

圖解:

情況1畫成圖就是:
在這裏插入圖片描述
圖中x到v的路徑異或和=1異或4異或5=0,那麼貢獻就是sz(v)乘上(標號爲1的部分+標號爲2的部分)
其中標號爲1的部分爲n-sz(x)+1,標號爲2的部分爲sz(x)-sz(k)-1。k是x到v這條鏈上,x的第一個兒子。
看圖應該很好理解

情況2畫成圖就是:
在這裏插入圖片描述
直接sz(x)乘上sz(v)就行了

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
const int mod=1e9+7;
unordered_map<int,int>mark1;//情況1
unordered_map<int,int>mark2;//情況2
int head[maxm],nt[maxm],to[maxm],w[maxm],cnt;
int sz[maxm];
int ans;
int n;
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
void dfs(int x){//計算sz
    sz[x]=1;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        dfs(v);
        sz[x]+=sz[v];
    }
}
void dfs1(int x,int val){//計算情況1答案
    ans=(ans+mark1[val]*sz[x])%mod;
    mark1[val]=(mark1[val]+(n-sz[x]+1))%mod;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        mark1[val]=(mark1[val]+(sz[x]-sz[v]-1))%mod;
        dfs1(v,val^w[i]);
        mark1[val]=(mark1[val]-(sz[x]-sz[v]-1)+mod)%mod;
    }
    mark1[val]=(mark1[val]-(n-sz[x]+1)+mod)%mod;//回溯的時候刪掉
}
void dfs2(int x,int val){//計算情況2答案
    ans=(ans+mark2[val]*sz[x]%mod)%mod;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        dfs2(v,val^w[i]);
    }
    mark2[val]=(mark2[val]+sz[x])%mod;
}
signed main(){
    cin>>n;
    for(int i=2;i<=n;i++){
        int fa,x;
        cin>>fa>>x;
        add(fa,i,x);
    }
    dfs(1);
    dfs1(1,0);
    dfs2(1,0);
    cout<<ans<<endl;
    return 0;
}

L.Swap

題意:

給長度爲n的數組,數組中的數兩兩不同
現在有兩種操作:
1.將所有偶數位置的數和前一個位置交換,如果長度爲奇數,則最後一位不變
2.將前一半數和後一半數交換,如果長度爲奇數,則中間位置不變
現在可以進行操作無限次,問數組一共有多少種不同的情況
n<=1e5

思路:

還是老實找規律吧。
打表程序是用bfs計算出全部方案數。
打表代碼和ac代碼都在下面。

打表代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxm=5e5+5;
map<string,int>mark;
int n;
void bfs(string st){
    queue<string>q;
    q.push(st);
    mark[st]=1;
    string temp;
    while(!q.empty()){
        string x=q.front();
        q.pop();
        temp=x;
        for(int i=1;i<n;i+=2){//奇偶交換
            swap(temp[i],temp[i-1]);
        }
        if(!mark[temp]){
            mark[temp]=1;
            q.push(temp);
        }
        temp=x;
        int l=0,r=n/2+1+(n%2)-1;
        while(l<n/2){//前後交換
            swap(temp[l],temp[r]);
            l++,r++;
        }
        if(!mark[temp]){
            mark[temp]=1;
            q.push(temp);
        }
    }
}
signed main(){
    for(n=1;n<=100;n++){//我把上線設爲了100
        mark.clear();
        string s;
        for(int i=0;i<n;i++){
            s+=(char)i;
        }
        bfs(s);
        cout<<"n="<<n<<','<<"ans="<<mark.size()<<endl;
    }
    return 0;
}

ac代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
int a[maxm];
signed main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int ans;
    if(n<=2){
        ans=n;
    }else if(n==3){
        ans=6;
    }else if(n%4==0){
        ans=4;
    }else if(n%4==1){
        ans=n*2;
    }else if(n%4==2){
        ans=n;
    }else if(n%4==3){
        ans=12;
    }
    cout<<ans<<endl;
    return 0;
}

M.Travel

題意:

n個點m條邊的無向圖,邊有邊權
現在有一個飛船,等級是0,可通過路徑大小爲0,可飛行次數爲0
可以花費c的費用給飛船升一級,升一級之後飛船的可通過路徑大小增加d,可飛行次數增加e
飛船隻能通過邊權小於可通過路徑大小的邊
問最少花費多少錢使得飛船可以從1到達n,輸出最少花費
如果1不能到達n則輸出-1

思路:

二分枚舉升級次數,在可通過的邊上跑最短路check就行了。因爲邊權爲1,也可以bfs。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章