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。