1. 程式人生 > >2016多校第10場

2016多校第10場

先附上題目連結:http://acm.split.hdu.edu.cn/search.php?field=problem&key=2016+Multi-University+Training+Contest+10&source=1&searchmode=source

1001 media

tag:二分

題意給你一個排好的n個數, 讓你求l1, r1, 和 l2, r2這兩區間合併起來的中位數。 

分析:我們可以很容易將這個問題轉化為第k問題, 因此我們二分這個數, 假設他是第k大那麼小於等於它的數的數量肯定大於等於k。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int maxn = 100000 + 100;
int a[maxn], Hash[maxn], d, b[maxn];
int n, m;

inline int get(int l, int r, int num) {    //小於等於num的個數
    int t =  upper_bound(b+l, b+r+1, num) - b-l;
    return t;
}

int query(int l1, int r1, int l2, int r2, int k) {   //求這兩個區間的第k小
    int l = 1, r = d;
    int res = -1;
    while(l <= r) {
        int mid = (l+r)/2;
        if(get(l1,r1,mid)+get(l2, r2,mid) >= k) {
            res = mid;
            r = mid - 1;
        }else l = mid + 1;
    }
    return res;
}

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &m);
        for(int i=1; i<=n; i++) {
            scanf("%d", &a[i]);
            Hash[i] = a[i];
        }
        sort(Hash+1, Hash+1+n);
        d = unique(Hash+1, Hash+1+n) - Hash - 1;
        for(int i=1; i<=n; i++)
            b[i] = lower_bound(Hash+1, Hash+1+n, a[i]) - Hash;
        while(m--) {
            int l1, r1, l2, r2;
            scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
            double res = 0;
            int num = r1-l1+r2-l2+2;
            if(num%2 == 1) {
                int idx = query(l1, r1, l2, r2, num/2+1);
                res = Hash[idx];
            }else{
                int idx1 = query(l1, r1, l2, r2, num/2);
                int idx2 = query(l1, r1, l2, r2, num/2+1);
                res = (double)Hash[idx1] + (double)Hash[idx2];
                res /= 2.0;
            }
            printf("%.1f\n", res);
        }
    }
    return 0;
}

1005 Road

題意: 有n個村莊, 村莊與村莊之間有n-1條道路相連線,現在告訴你第i天需要從村莊a到村莊b運送物資, 因此你需要保證第i天村莊a到村莊b的道路是可用的, 剛開始所有的道路都不可用,你可以選擇一條路讓他可用或者不可用,但是一條道路只能開或者關一次, 每一條道路每執行一天需要花費一些錢, 現在問你第i天的花費。

tag: 線段樹 + 轉換思維

分析:首先考慮每一路, 我們需要維護出這條路最早開始和最晚關閉的時間(可以用線段樹統計)。 然後我們在考慮每一天, 我們肯定可以處理處第i天需要開啟哪些道路和關閉那些道路, 這樣就可以用線段樹快速的算出答案。 感覺應該是用了兩個線段樹。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;
typedef pair<int, int> Pii;
const int maxn = 200000 + 100;
const int inf = 0x3f3f3f3f;

struct Segment{
    int l, r;
    int mi, ma;   //區間最小值 和 最大值
    int sum;
}seg[maxn*3];

void build(int rt, int l, int r) {
    seg[rt].l = l; seg[rt].r = r;
    seg[rt].mi = inf; seg[rt].ma = -1;
    seg[rt].sum = 0;
    if(l == r) return ;
    int mid = (l+r) / 2;
    build(2*rt, l, mid);
    build(2*rt+1, mid+1, r);
}

inline void push_down(int rt) {
    seg[2*rt].ma = max(seg[rt].ma, seg[2*rt].ma);
    seg[2*rt+1].ma = max(seg[rt].ma, seg[2*rt+1].ma);
    seg[2*rt].mi = min(seg[rt].mi, seg[2*rt].mi);
    seg[2*rt+1].mi = min(seg[rt].mi, seg[2*rt+1].mi);
}

void update_mima(int rt, int l, int r, int x) {
    if(seg[rt].l==l && seg[rt].r==r) {
        seg[rt].mi = min(seg[rt].mi, x);
        seg[rt].ma = max(seg[rt].ma, x);
        return ;
    }
    push_down(rt);
    int mid = (seg[rt].l + seg[rt].r)/2;
    if(r <= mid)
        update_mima(2*rt, l, r, x);
    else if(l > mid)
        update_mima(2*rt+1, l, r, x);
    else{
        update_mima(2*rt, l, mid, x);
        update_mima(2*rt+1, mid+1, r, x);
    }
}

Pii queryday(int rt, int i){    //查詢第i段路的最早開始時間和最晚結束時間
    if(seg[rt].l == seg[rt].r) {
        return make_pair(seg[rt].mi, seg[rt].ma);
    }
    push_down(rt);
    int mid = (seg[rt].l + seg[rt].r)/2;
    if(i <= mid)
        return queryday(2*rt, i);
    else
        return queryday(2*rt+1, i);
}

void update_sum(int rt, int sg, int num) {    //將sg這一段路置為num
    if(seg[rt].l == seg[rt].r){
        seg[rt].sum = num;
        return ;
    }
    int mid = (seg[rt].l + seg[rt].r) / 2;
    if(sg <= mid)
        update_sum(2*rt, sg, num);
    else
        update_sum(2*rt+1, sg, num);
    seg[rt].sum = seg[2*rt].sum + seg[2*rt+1].sum;
}

int n, m;
int w[maxn];
vector<int> dayst[maxn], dayed[maxn];
int main() {
    while(scanf("%d%d", &n, &m) == 2){
        for(int i=1; i<=n-1; i++) scanf("%d", &w[i]);
        build(1, 1, n-1);
        for(int i=1; i<=m; i++) {
            dayst[i].clear(); dayed[i].clear();
            int l, r; scanf("%d%d", &l, &r);
            if(l > r) swap(l, r);
            update_mima(1, l, r-1, i);
        }
        dayed[m+1].clear();
        for(int i=1; i<=n-1; i++) {
            Pii tp = queryday(1, i);
            //printf("%d %d\n", tp.first, tp.second);
            if(tp.first != inf) dayst[tp.first].push_back(i);
            if(tp.second != -1) dayed[tp.second+1].push_back(i);
        }
        for(int i=1; i<=m; i++) {
            for(int j=0; j<dayst[i].size(); j++) {
                int sg = dayst[i][j];
                update_sum(1, sg, w[sg]);
            }
            for(int j=0; j<dayed[i].size(); j++) {
                int sg = dayed[i][j];
                update_sum(1, sg, 0);
            }
            printf("%d\n", seg[1].sum);
        }
    }
    return 0;
}

tag:掃描線

題意:給你一些平面上平行於座標軸的線段問你這些線段的交叉點有多少個, 多個線段交於同意交叉點出必須計算多次

分析:一道很好的掃描線的題。  詳解在這

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;
const int maxn = 100000 + 100;
typedef long long LL;
int n;
struct node{
    int type, x, y1, y2;    //ÀàÐÍ 0 ºáÏß  1 ÊúÏß
    node() {}
    node(int type, int x, int y1, int y2):type(type), x(x), y1(y1), y2(y2) {}
    bool operator < (const node& r) const {
        if(x == r.x) return type < r.type;
        return x < r.x;
    }
}nd[2*maxn];
int nnd;

int c[2*maxn], nc;
inline int lowbit(int x) {
    return x&(-x);
}
int sum(int i) {
    int s = 0;
    while(i > 0) {
        s += c[i];
        i -= lowbit(i);
    }
    return s;
}

void add(int i, int val) {
    while(i <= nc) {
        c[i] += val;
        i += lowbit(i);
    }
}

int Hash[2*maxn], nHash;
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        nnd = 0;
        nHash = 0;
//        cout<<nHash<<endl;
        for(int i=0; i<n; i++) {
            int x1, y1, x2, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            if(x1 == x2) {
                if(y1 > y2) swap(y1, y2);
                nd[++nnd] = node(1, x1, y1, y2);
                Hash[++nHash] = y1;
                Hash[++nHash] = y2;
            }else{
                if(x1 > x2) swap(x1, x2);  //x1 < x2
                nd[++nnd] = node(0, x1, y1, 1);
                nd[++nnd] = node(0, x2+1, y1, -1);
                Hash[++nHash] = y1;
            }
        }
//        cout<<nHash<<endl;
        sort(Hash+1, Hash+1+nHash);
        int tp = unique(Hash+1, Hash+1+nHash) - Hash - 1;
        nHash = tp;
        sort(nd+1, nd+1+nnd);
        LL res = 0;
        memset(c, 0, sizeof(c));
        nc = nHash;
        for(int i=1; i<=nnd; i++) {
            if(nd[i].type == 0) {
                int tp = lower_bound(Hash+1, Hash+1+nHash, nd[i].y1) - Hash;
                add(tp, nd[i].y2);
            }else{
                int tp1 = lower_bound(Hash+1, Hash+1+nHash, nd[i].y1) - Hash;
                int tp2 = lower_bound(Hash+1, Hash+1+nHash, nd[i].y2) - Hash;
                res += sum(tp2) - sum(tp1-1);
            }
        }
        printf("%lld\n", res);
    }
    return 0;
}
1007 cjj's string game

tag:dp 計數

題意:定義兩個字串的cjj_val = max對應位置相同的長度最大值, 現在計算cjj_val = m的長度為n的字串, 可以使用K個不同字元。

分析:我們定義dp[i][j]為已經構造了長度為i, 且後面有j個字元一樣的方案數, 那麼dp[i][j] = dp[i-1][j-1]*k(最後一個一樣的字元有k種選擇),特別的dp[i][0] = sigma(dp[i-1][j])*(k*(k-1)) 0<=j<=m, (最後一個字元不一樣有(k*(k-1)))中方案,那麼不超過cjj_val不超過m的方案數為sigma(dp[n][j]), 減去不超過m-1的方案數就是恰好為m的方案數, 由於m很小因此我們使用矩陣快速冪快速計算dp值。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

using namespace std;
typedef long long LL;
const LL M = 1000000007;


struct Mat{
    int sz;
    LL mat[30][30];
};

void debug(Mat a) {
    printf("debug =====   \n");
    for(int i=1; i<=a.sz; i++) {
        for(int j=1; j<=a.sz; j++)
            printf("%I64d ", a.mat[i][j]);
        printf("\n");
    }
}

Mat operator* (const Mat a, const Mat b) {
    Mat res;
    res.sz = a.sz;
    memset(res.mat, 0, sizeof(res.mat));
    for(int i=1; i<=a.sz; i++)
    for(int j=1; j<=b.sz; j++)
    for(int k=1; k<=a.sz; k++){
        res.mat[i][j] += a.mat[i][k] * b.mat[k][j];
        res.mat[i][j] %= M;
    }
    return res;
}

Mat qk_mod(Mat a, LL n) {
    Mat res;
    res.sz = a.sz;
    memset(res.mat, 0, sizeof(res.mat));
    for(int i=1; i<=a.sz; i++) res.mat[i][i] = 1;
//    printf("qk_mod\n");
//    debug(res);
    while(n) {
        if(n&1)
            res = res * a;
        a = a*a;
        n >>= 1;
    }
    return res;
}



LL solve(LL n, LL m, LL k) {
//    cout<<"k = "<<k<<endl;
    Mat res;
    res.sz = m + 1;
    memset(res.mat, 0, sizeof(res.mat));
    for(int j=1; j<=m+1; j++) res.mat[1][j] = k*(k-1);
    for(int i=2; i<=m+1; i++) res.mat[i][i-1] = k;
//    debug(res);
    res = qk_mod(res, n);
//    debug(res);
    LL ans = 0;
    for(int i=1; i<=m+1; i++) {
        ans += res.mat[i][1];
        ans %= M;
    }
    return ans;
}

int main() {
    LL n, m, k;
    int T;
    scanf("%d", &T);
    while(T--) {
        cin>>n>>m>>k;
//        printf("n = %I64d, m = %I64d, k = %I64d\n", n, m, k);
        LL res = solve(n, m, k) - solve(n, m-1, k);
        res = (res%M + M)%M;
        cout<<res<<endl;
    }
    return 0;
}