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。


发布了430 篇原创文章 · 获赞 36 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/104891016