NOIP 2018訓練賽第二場
正題
A. inform
一場天災過後,B 市的所有主幹道路都被切斷了。災後重建的一項重要任務是恢復通訊。B 市共有 nn 個關鍵的據點,而我們現在有一條關鍵的訊息,需要所有的據點都要收到。
訊息的傳遞有兩種方式:
空降:可以直接將訊息傳給某個據點,每次需要的代價為 vv。
通訊員:可以將訊息從一個據點傳到另一個據點,需要的代價為兩個據點在地圖上的歐氏距離的平方。保證所有點的座標均為整數,所以這個代價也一定是整數。
注意,通訊員只能從已有訊息的據點傳遞訊息到另一個據點。所以,至少第一個收到訊息的據點一定是通過空降的。
在保證所有的據點都收到訊息的前提下,最小的總代價是多少?
所有測試點的 n 分別為:1, 5, 9, 13, 17, 50, 300, 1000, 3000, 5000。
對於所有資料,保證 0≤v≤100,000,0≤x,y≤30,0000≤v≤100,000,0≤x,y≤30,000。
很明顯,n的平方的最小生成樹可以通過此題,prim即可。
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; int n,k; int r[5010][5010]; int first[5010]; long long dis[5010]; bool tf[5010]; struct node{ long long x,y; }p[5010]; int len=0; int d[5010]; int main(){ scanf("%d %d",&n,&k); for(int i=1;i<=n;i++) scanf("%d %d",&p[i].x,&p[i].y); int temp; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ temp=(p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y); r[i][j]=r[j][i]=temp; } dis[1]=0;tf[1]=true; for(int i=2;i<=n;i++) dis[i]=r[1][i]; int tot=2; int mmin=2e9,id; long long ans=k; while(tot<=n){ mmin=2e9; for(int i=1;i<=n;i++) if(tf[i]==false && dis[i]<mmin) mmin=dis[i],id=i; tf[id]=true; tot++; if(dis[id]>k) ans+=k; else ans+=dis[id]; for(int i=1;i<=n;i++) if(tf[i]==false) dis[i]=min(dis[i],(long long)r[id][i]); } printf("%lld\n",ans); }
B. treelink
D 國有 nn 個城市,有若干條道路,每條道路能連線兩個城市,並且有一定的長度。
可是……初始時,並沒有任何道路存在。接下來,有 qq 個操作需要你依次完成:
x y 表示:詢問城市 xx 與 yy 之間的最短路徑長度;如果不存在任何路徑,則你應當回答-1。
x y w 表示:在城市 xx 與 yy 之間修建了一條長度為 ww 的道路。保證在此之前在城市 xx 與 yy 之間不存在任何路徑(即:假如在此之前給出一個 xx yy 的操作,保證其答案應當為-1)。
離線,對於每一條邊,我們記錄下他加入的時間和長度。
然後,如果當前的查詢沒有超過這條鏈建立的時間,那麼就輸出,否則就是-1.
但是,雖然標程是nlogn的,但是我的兩個log還是卡了過去。
就是直接啟發式合併,然後維護lca,所以加一條邊是兩個log的,查詢是一個log的,就是比較慢。在比賽中我卻因為要卡常改了一些東西,然後就錯了。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n,q;
char a[110];
struct ques{
int x,y,w;
}p[500010];
struct edge{
int y,next,c;
}s[1000010];
int l=0;
int size[500010],fa[500010],dep[500010],first[500010],dis[500010];
int f[500010][20];
int len=0;
void ins(int x,int y,int c){
len++;
s[len].y=y;s[len].c=c;s[len].next=first[x];first[x]=len;
}
void update(int y,int x,int c){
dis[y]=dis[x]+c;dep[y]=dep[x]+1;
f[y][0]=x;
for(int k=1;k<=19;k++) f[y][k]=f[f[y][k-1]][k-1];
}
void dfs(int x){
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y!=f[x][0]){
update(y,x,s[i].c);
dfs(y);
}
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int get_ans(int x,int y){
return dis[x]+dis[y]-2*dis[lca(x,y)];
}
int findpa(int x){
if(fa[x]!=x) return fa[x]=findpa(fa[x]);
return x;
}
int main(){
scanf("%d %d\n",&n,&q);
for(int i=1;i<=n;i++) size[i]=1,fa[i]=i;
int x,y,w,now;
int tot=0;//詢問總數
bool tf=false;
for(int i=1;i<=q;i++){
gets(a+1);
l=strlen(a+1);
x=y=w=now=0;
for(int k=1;k<=l;k++){
if(a[k]!=' ') now=now*10+a[k]-'0';
else{
if(x==0) x=now;
else if(y==0) y=now;
else if(w==0) w=now;
now=0;
}
}
if(y==0) y=now;
else if(w==0) w=now;
p[i].x=x;p[i].y=y;p[i].w=w;
if(w==0) tot++;
}
for(int i=1;i<=q;i++){
if(p[i].w==0 && !tf) {
printf("-1\n");
if(tot==i) return 0;
continue;
}
tf=true;
if(p[i].w==0) {
if(findpa(p[i].x)==findpa(p[i].y)) printf("%d\n",get_ans(p[i].x,p[i].y));
else printf("-1\n");
}
else {
int fx=findpa(p[i].x),fy=findpa(p[i].y);
if(size[fx]<size[fy]) swap(p[i].x,p[i].y),swap(fx,fy);
fa[fy]=fx;
size[fx]+=fy;
update(p[i].y,p[i].x,p[i].w);
ins(p[i].x,p[i].y,p[i].w);
ins(p[i].y,p[i].x,p[i].w);
dfs(p[i].y);
}
}
}
C. word
我想讓你告訴我一個數字串……
這個串的長度必須是 nn,並且每一位都是 1 到 kk 的數字。(1≤k≤91≤k≤9)
我還會給你 mm 條“禁止規則”。對於第 ii 條規則,我會給你兩個數字 aiai 與 bibi,表示數字 aiai 禁止出現在數字 bibi 之前。
現在,我要問你以下兩個問題:
一共有多少種你可以告訴我的數字串?
如果把數字串看成一個整數,那麼所有可能的數字串作為整數的和是多少?
對於測試點 1,保證 n=1n=1。
對於測試點 2,3,4,保證 n≤6。
對於測試點 5,6,保證 n≤50。
對於測試點 7,8,保證 n≤200。
對於所有編號為奇數的測試點,保證 m=0。
對於全部資料,保證 1≤n≤500,0≤m≤100。
這題比較良心,m=0的時候可以直接算答案,前4個點暴力可以過。所以就有70分了。
正解是這樣的。
表示已選i個,可選集合為j的數的種數。
表示已選i個,可選集合為j的數的總和。
那麼很容易就可以知道方程: