2006-2007 ACM-ICPC | POJ3380 POJ3384 POJ3385 水題題解


 // CF比賽鏈接:http://codeforces.com/gym/101650

 // POJ鏈接:http://poj.org/searchproblem?field=source&key=Northeastern+Europe+2006,POJ3379 ~ POJ3389

 // Day12 暑訓第一階段最后一場組隊賽

 // 區域賽難度,表現還可以,前期互相推鍋,后半場自閉O.O

B - Bridges

題目大意:

    n個地區之間有n-1條道路,兩兩之間只有一條路徑(說明是一個樹結構)。初始的道路只有馬能走,需要修建k個橋能讓車通過,給定馬速sh與車速sc,以及地圖信息,為了使任意兩節點之間所花時間減少最多,求修建k座橋的標號。

分析及代碼:

    很水的,很容易發現樹上每條邊走過的次數為兩端點子樹的總節點個數相乘,要使減少時間最大化,顯然應直接選擇邊長*次數最大的邊。

    然而測試數據有坑,車速可能比馬速還要小,就老老實實算減少時間,排序后選最大的吧。。。

 

    AC代碼采取了樹上dfs的兩種寫法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long  ll;
const int maxn = 10100;
struct Edge {
    int to, id, next;
    double w;
}edges[maxn*2];
int head[maxn*2], tot;
int n, k, sh, sc;
void addEdge(int u, int v, double w, int id) {
    edges[tot].to = v;
    edges[tot].w = w/sh - w/sc;  // 只記錄邊長會WA,還是按照題意算減少時間吧
    edges[tot].id = id;
    edges[tot].next = head[u];
    head[u] = tot++;
}
struct node {
    int id;
    double w;
    node (int _id, double _w):id(_id), w(_w) {}
    bool operator<(const node& a)const {
        return w<a.w;      // 重載小於號,默認大頂堆
    }
};
priority_queue<node> ans;
int cnt;
void dfs(int u, int fa) {
    ++cnt;
    for(int i=head[u];~i;i=edges[i].next) {
        int v = edges[i].to;
        if(v!=fa) {
            int no = cnt;
            //cout<<v<<endl;
            dfs(v, u);
            //edges[i].w *= (ll)(cnt-no)*(n-(cnt-no));
            int son = cnt - no;    // cnt已更新到葉子節點,兩者差為以v節點為根的子樹節點總個數
            double times = (son+0.0) * (n - son);
            ans.push(node(edges[i].id, times*edges[i].w));
            //printf("%d->%d:%d\n", u, v, w);
        }
    }
}
 
 
int main() {
    
    cin>>n>>k>>sh>>sc;
    memset(head, -1, sizeof(head));
    for(int i=1;i<n;i++) {
        int u, v, l;
        scanf("%d %d %d", &u, &v, &l);
        addEdge(u, v, l, i);
        addEdge(v, u, l, i);
    }
 
    cnt = 0;
    dfs(1, -1);
 
    for(int i=1;i<=k;i++) {
        printf("%d%c", ans.top().id, i==k?'\n':' ');
        ans.pop();
    }
    return 0;
}
View Code

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long  ll;
const int maxn = 10100;
struct Edge {
    int to, id, next;
    double w;
}edges[maxn*2];
int head[maxn*2], tot;
int n, k, sh, sc;
void addEdge(int u, int v, double w, int id) {
    edges[tot].to = v;
    edges[tot].w = w;
    edges[tot].id = id;
    edges[tot].next = head[u];
    head[u] = tot++;
}
struct node {
    int id;
    double w;
    node (int _id, double _w):id(_id), w(_w) {}
    bool operator<(const node& a)const {
        return w>a.w;
    }
};
vector<node> ans;
bool vis[maxn];
int cnt;
void dfs(int u) {
    vis[u] = 1;
    ++cnt;
    for(int i=head[u];~i;i=edges[i].next) {
        int v = edges[i].to;
        if(!vis[v]) {
            int no = cnt;
            //cout<<v<<endl;
            dfs(v);
            //edges[i].w *= (ll)(cnt-no)*(n-(cnt-no));
            int son = cnt - no;
            double times = (son+0.0) * (n - son);
            ans.push_back(node(edges[i].id, times*edges[i].w));
            //printf("%d->%d:%d\n", u, v, w);
        }
    }
}


int main() {
    
    cin>>n>>k>>sh>>sc;
    memset(head, -1, sizeof(head));
    for(int i=1;i<n;i++) {
        int u, v, l;
        scanf("%d %d %d", &u, &v, &l);
        addEdge(u, v, l, i);
        addEdge(v, u, l, i);
    }

    cnt = 0;
    dfs(1);
    sort(ans.begin(), ans.end());
    for(int i=0;i<k;i++) {
        printf("%d%c", ans[i].id, i==k?'\n':' ');
    }
    return 0;
}
View Code

 

 

F - Feng Shui

題目大意:

    在一塊多邊形區域鋪上兩個相同半徑的圓形地毯,要使地毯最多與邊界相切(不能相交)的情況下,地毯覆蓋的面積最大,求兩圓心的位置坐標。

分析及代碼:

    隊友分析出,只需要把多邊形的每條邊往里平移r,得到新的多邊形,圓心位置一定在新的多邊形端點上,所求圓心為兩點之間的距離最長的那兩個端點。這樣既保證了與邊界不相交,求兩圓形地毯重疊最少,所以覆蓋面積最大。

    比賽抄了部分模板,一直WAWA大哭,自閉到比賽結束。

    賽后才想到平移之后有的邊會消失(移動后的邊在多邊形外部,如五邊形縮小成為了四邊形),直接求相鄰兩邊得到新的多邊形端點是不對的。

    那么該如何修正呢?雖然發現這些邊方向會反轉,但判斷起來太麻煩,因為刪去一條邊還會影響下一條邊。即使算法正確,還需要確定第一條一定相切的邊,否則也可能是要刪去的邊。

    其實這題就是半平面交的裸題。

 

    附上計算幾何模板+AC代碼:(把點乘敲錯讓我debug了一下午。。。)

#include<cstdio>
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;

// ***********************模板開始****************************
struct Point {
    double x, y;
    Point (double xx=0, double yy=0):x(xx), y(yy) {}
};
typedef Point Vector;
typedef vector<Point> Polygon;

Vector operator+(Point A, Vector B) {
    return Vector(A.x+B.x, A.y+B.y);
}
Vector operator-(Point A, Vector B) {
    return Vector(A.x-B.x, A.y-B.y);
}
Vector operator*(Vector A, double t) {
    return Vector(A.x*t, A.y*t);
}
// 向量叉乘
double cross(Vector A, Vector B) {
    return A.x*B.y - A.y*B.x;
}
// 向量點乘
double dot(Vector A, Vector B) {
    return A.x*B.x + A.y*B.y;
}
// 與0比較函數
const double eps = 1e-10;
int dcmp(double x) {
    if(fabs(x)<eps) return 0;
    return x<0? -1:1;
}
// p是否在線段AB上
bool onSegment(Point p, Point A, Point B) { // 包含端點
    return dcmp(cross(A-p, B-p))==0 && dcmp(dot(A-p, B-p))<=0;
}
// 兩直線交點
Point crossPoint(Point P, Vector v, Point Q, Vector w) {
    Vector u = P-Q;
    double t = cross(w, u)/cross(v, w);
    return P+v*t;
}
// 半平面交模板,直線AB切割多邊形poly,返回左側部分
Polygon cutPolygon(Polygon poly, Point A, Point B, int k) {
    Polygon newpoly;
    int n = poly.size();
    for(int i=0;i<n;i++) {
        Point C = poly[i];
        Point D = poly[(i+1)%n];
        if(dcmp(cross(B-A, C-A))>=0) newpoly.push_back(C);
        if(dcmp(cross(B-A, C-D))!=0) { // C-D與A-B不平行
            Point ip = crossPoint(A, B-A, C, D-C);
            if(onSegment(ip, C, D)) newpoly.push_back(ip);
        }
    }
    return newpoly;
}
// ***********************模板結束****************************

Point p[110];

// AB距離
double dis(Point A, Point B) {
    double x =  A.x - B.x;
    double y = A.y - B.y;
    double res = x*x + y*y;
    return sqrt(res);
}

Polygon init(int n) {
    Polygon poly;
    for(int i=0;i<n;i++) {
        poly.push_back(p[i]);
    }
    return poly;
}

int main() {
    int n;
    double x, y, r;
    cin>>n>>r;
    for(int i=n-1;i>=0;i--) {
        scanf("%lf %lf", &x, &y);
        p[i].x = x;
        p[i].y = y; 
    }
    p[n] = p[0];

    Polygon poly = init(n);

    for(int i=0;i<n;i++) {
        double len = dis(p[i], p[i+1]);
        double dx = p[i+1].x - p[i].x;
        double dy = p[i+1].y - p[i].y;

        Point A = Point(p[i].x - r*dy/len, p[i].y + r*dx/len);
        Point B = Point(p[i+1].x - r*dy/len, p[i+1].y + r*dx/len);
 
        poly = cutPolygon(poly, A, B, i);
    }
 
    double ans = 0;
    int p1 = -1, p2 = -1;
    for(int i=0;i<poly.size();i++) {
        for(int j=i+1;j<poly.size();j++) {
            if(ans<dis(poly[i], poly[j])) {
                p1 = i; p2 =j;
                ans = dis(poly[i], poly[j]);
            }
        }
    }
    if(p1!=-1) {
        printf("%.5lf %.5lf %.5lf %.5lf\n", poly[p1].x, poly[p1].y, poly[p2].x, poly[p2].y);
    } else { // 縮成一個點,兩圓重合
        printf("%.5lf %.5lf %.5lf %.5lf\n", poly[0].x, poly[0].y, poly[0].x, poly[0].y);
    }
    return 0;
}
View Code

 

 

G - Genealogy

題目大意:

    給你一個家譜圖組成的樹,要使每個家庭成員的兒子節點不超過d個且不改變祖先-子孫關系,求最少需要加入幾個節點。

分析及代碼:

    翻譯成數據結構的語言,本題就是n-叉樹的轉化問題。

    直接記錄每個節點兒子的數量,然后計算有超過d個兒子的節點經過變換需要添加的節點個數即可。

    AC代碼就是不斷把d個兒子連上新的節點,並作為新的兒子,直到新的兒子總個數小於等於d(注意不是1啊,讓我16組T了!)

    四行代碼就夠了:

int res = 0;

while(k>d) {

  res += k/d;

  k = k/d + k%d;

}

     AC代碼:

#include<cstdio>
#include<iostream>
using namespace std;
 
 
int n, d;
int son[100100];
 
int cal(int k) {
    int res = 0;
    while(k>d) {
        res += k/d;
        k = k/d + k%d;
    //    cout<<k<<endl;
    }
    return res;
 
}
int main() {
    //while(cin>>n>>d) cout<<cal(n)<<endl;
    cin>>n>>d;
    for(int i=1;i<=n;i++) {
        int u;
        scanf("%d", &u);
        son[u]++;
    }
    int ans=0;
    for(int i=0;i<=n;i++) {
        if(son[i]>d) {
            ans += cal(son[i]);
        }
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

 


 

    (未完待續)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM