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。