2016多校第10场

时间:2022-11-05 05:29:23

先附上题目链接: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;
}

1006 Counting Intersections

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;
}