高二&高一&初三模拟赛23 总结

前言

意识到比赛的时候已经过去了一个多小时;心情低躁;于是成功弃赛。。


Hacker

高二&高一&初三模拟赛23 总结_第1张图片
高二&高一&初三模拟赛23 总结_第2张图片


题解(二分/treap)

看完这题一直在想O(n)的方法,最后只想到一个O(nlogn)的方法。我们对于每个节点都以左端点建一棵treap,然后用右端点去找最小值即可。然而这样莫名被卡掉2个点。(treap的常数?!)

题解的作法是排序+二分,方法也差不多,只需维护一个后缀最小值即可。

另外还有用链表的O(n)作法,去看KsCla的博客吧。


代码(treap)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 1000010
#define oo 0x7fffffff
#define _min(a, b) (((a) < (b)) ? (a) : (b))

using namespace std;

int n, x, cnt, ans = oo;

struct Data{
    int L, R, cost, day;
}plan[maxn];

struct Treap{
    Treap *L, *R;
    int val, cost, Min, fix;
    int Lmin(){return L ? L->Min : oo;}
    int Rmin(){return R ? R->Min : oo;}
    void Up(){
        Min = _min(cost, _min(Lmin(), Rmin()));
    }
}Node[maxn], *Root[maxn];

Treap *NewTnode(int val, int cost){
    Node[cnt].L = Node[cnt].R = NULL;
    Node[cnt].val = val;
    Node[cnt].cost = Node[cnt].Min = cost;
    Node[cnt].fix = rand();
    return Node+cnt++;
}

void Treap_L_Rot(Treap *&a){
    Treap *b = a->R;
    a->R = b->L;
    b->L = a;
    a = b;
    a->L->Up();
    a->Up();
}

void Treap_R_Rot(Treap *&a){
    Treap *b = a->L;
    a->L = b->R;
    b->R = a;
    a = b;
    a->R->Up();
    a->Up();
}

void Treap_Insert(Treap *&p, int val, int cost){
    if(!p)  p = NewTnode(val, cost);
    else if(val == p->val){
        p->cost = _min(p->cost, cost);
        p->Min = _min(p->Min, cost);
    }
    else if(val < p->val){
        Treap_Insert(p->L, val, cost);
        p->Min = _min(p->Min, cost);
        if(p->L->fix < p->fix)  Treap_R_Rot(p);
    }
    else{
        Treap_Insert(p->R, val, cost);
        p->Min = _min(p->Min, cost);
        if(p->R->fix < p->fix)  Treap_L_Rot(p);
    }
}

int Treap_Work(Treap *p, int val){
    if(!p)  return oo;
    if(p->val > val)  return _min(_min(p->Rmin(), p->cost), Treap_Work(p->L, val));
    else  return Treap_Work(p->R, val);
}

int main(){

    scanf("%d%d", &n, &x);

    for(int i = 1; i < x; i++)  Root[i] = NULL;

    for(int i = 1; i <= n; i++){
        scanf("%d%d%d", &plan[i].L, &plan[i].R, &plan[i].cost);
        plan[i].day = plan[i].R - plan[i].L + 1;
        if(plan[i].day < x)  Treap_Insert(Root[plan[i].day], plan[i].L, plan[i].cost);
    }

    for(int i = 1; i <= n; i++){
        if(plan[i].day >= x)  continue;
        int temp = Treap_Work(Root[x-plan[i].day], plan[i].R);
        if(temp == oo)  continue;
        ans = _min(ans, plan[i].cost + temp);
    }

    if(ans == oo)  ans = -1;
    printf("%d\n", ans);
    return 0;
}

代码(二分)

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 1000010
#define oo 0x7fffffff

using namespace std;

int n, x, Min[maxn], ans = oo;
struct Data{
    int L, R, day, cost;
    bool operator < (const Data& Q) const{
        if(day == Q.day)  return L < Q.L;
        return day < Q.day;
    }
}plan[maxn];

bool check(int a, int b){
    return plan[a].day + plan[b].day > x || (plan[a].day + plan[b].day == x && plan[b].L > plan[a].R);
}

int main(){

    scanf("%d%d", &n, &x);

    for(int i = 1; i <= n; i++){
        scanf("%d%d%d", &plan[i].L, &plan[i].R, &plan[i].cost);
        plan[i].day = plan[i].R - plan[i].L + 1;
    }

    sort(plan+1, plan+n+1);

    for(int i = n, j = n; i > 0; i = j){
        while(plan[j].day == plan[i].day){
            if(j == i)  Min[j] = plan[j].cost;
            else  Min[j] = min(plan[j].cost, Min[j+1]);
            j --;
        }
    }

    for(int i = 1; i <= n; i++){
        if(plan[i].day >= x)  continue;
        int L = 0, R = n + 1;
        while(L + 1 < R){
            int mid = (L + R) >> 1;
            if(check(i, mid))  R = mid;
            else  L = mid;
        }
        if(R <= n && plan[i].day + plan[R].day == x)  
            ans = min(ans, plan[i].cost + Min[R]);
    }

    if(ans == oo)  ans = -1;
    printf("%d\n", ans);

    return 0;
}

记字符串

高二&高一&初三模拟赛23 总结_第3张图片
高二&高一&初三模拟赛23 总结_第4张图片
高二&高一&初三模拟赛23 总结_第5张图片


题解(状压DP)

这题是一道状压DP的好题。看了题解我才能做出来。

我们考虑将第i的字符串的第j位换掉,为了做到这个,我们可以直接将第i的字符串的第j位换成一个合法的字符,因为只有20个串,有26个字符,所以肯定可以。这样只影响自己的状态,将这位变成1,我们记当前已经独特的为1。另外的转移就是可以将其他串这一位和它一样的换掉,然后这样除了最贵的那个全换就行了,也影响了其他的状态。为了避免重复转移,确定的转移次序就是最后一个0(套路同2016 愤怒的小鸟)。然后就是一大堆的预处理了(见代码)。

时间复杂度 O(m2n+n2m)


代码

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 22

using namespace std;

int n, m, start;

char str[maxn][maxn];
int cost[maxn][maxn], sum[maxn][maxn], Max[maxn][maxn], sam[maxn][maxn];
int dp[1<int main(){

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%s", &str[i]);

    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &cost[i][j]);

    for(int i = 1; i <= n; i++){
        bool easy = false;
        for(int j = 1; j <= m; j++){
            bool f = true;
            for(int k = 1; k <= n; k++){
                if(str[i][j-1] == str[k][j-1]){  
                    sum[i][j] += cost[k][j];
                    sam[i][j] |= (1<<(k-1));
                    Max[i][j] = max(Max[i][j], cost[k][j]);
                    if(i != k)  f = false;
                }
            }
            if(f)  easy = true;
        }
        if(easy)  start |= (1 << (i-1));
    }


    memset(dp, -1, sizeof(dp));
    dp[start] = 0; 

    for(int s = start; s < (1<1; s++){
        if(dp[s] == -1)  continue;
        int low;
        for(int i = 1; i <= n; i++)
            if(!((1<<(i-1)) & s)){
                low = i;
                break;
            }

        int ns;
        for(int i = 1; i <= m; i++){
            ns = s | (1<<(low-1));
            if(dp[ns] == -1)  dp[ns] = dp[s] + cost[low][i];
            else  dp[ns] = min(dp[ns], dp[s] + cost[low][i]);

            ns = s | sam[low][i];
            if(dp[ns] == -1)  dp[ns] = dp[s] + sum[low][i] - Max[low][i];
            else  dp[ns] = min(dp[ns], dp[s] + sum[low][i] - Max[low][i]);
        }
    }

    printf("%d\n", dp[(1<1]);

    return 0;
}

士兵与旅行

高二&高一&初三模拟赛23 总结_第6张图片
高二&高一&初三模拟赛23 总结_第7张图片
高二&高一&初三模拟赛23 总结_第8张图片


题解(网络流)

网络流的裸题,初三打ACM的时候做过的,当时我的两个队友忘了拆点,拆完点最后WAWAWA,忘了考虑“人间蒸发”的情况。

其他的就是水题,从源向汇流,按图连边,判断能否满流即可,输出方案的话,由于直接最大流,于是发现每条边流了多少实际就是多少,于是反向边的容量就是方案。

多路增广有点不熟。


代码

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 105
#define maxm 205
#define INF 0x7fffffff
using namespace std;

int n, m, s, t, sumA, sumB;
int a[maxn], b[maxn], path[maxn][maxn];
int head_p[maxn<<1], level[maxn<<1], iter[maxn<<1], q[maxn<<1], cur = -1;
struct Adj {int next, cap, obj;} Edg[(maxn*6)+(maxm*4)];

void Insert(int a, int b, int c){
    cur ++;
    Edg[cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].cap = c;
    head_p[a] = cur;
}

int Dinic(int now, int f){
    if(now == t || !f)  return f;
    int ret = 0;
    for(int &i = iter[now]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj, c = Edg[i].cap;
        if(c && level[v] > level[now]){
            int d = Dinic(v, min(f, c));
            Edg[i].cap -= d;
            Edg[i^1].cap += d;
            f -= d;
            ret += d;
            if(!f)  break;
        }
    }
    return ret;
}

bool bfs(){
    memset(level, -1, sizeof(level));
    level[s] = 0;
    q[0] = s;
    int head = 0, tail = 0;

    while(head <= tail){
        int now = q[head++];
        for(int i = head_p[now]; ~ i; i = Edg[i].next){
            int v = Edg[i].obj, c = Edg[i].cap;
            if(c && level[v] == -1){
                level[v] = level[now] + 1;
                q[++tail] = v;
            }
        }
    }   
    return level[t] != -1;
}

int Max_flow(){
    int flow = 0;
    while(bfs()){
        memcpy(iter, head_p, sizeof(iter));
        flow += Dinic(s, INF);
    }
    return flow;
}

void Print(){

    for(int i = 2; i <= n+1; i++)
        for(int j = head_p[i]; ~ j; j = Edg[j].next){
            int v = Edg[j].obj;
            if(v > n && v <= (n << 1 | 1))  path[i-1][v-n-1] = Edg[j^1].cap;
        }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++)
            printf("%d ", path[i][j]);
        printf("\n");
    }
}

int main(){
    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        sumA += a[i];
    }
    for(int i = 1; i <= n; i++){
        scanf("%d", &b[i]);
        sumB += b[i];
    }

    s = 1;  t = s + (n << 1) + 1;

    memset(head_p, -1, sizeof(head_p));

    for(int i = 1; i <= n; i++){
        Insert(s, s+i, a[i]);
        Insert(s+i, s, 0);
    }
    for(int i = 1; i <= n; i++){
        Insert(s+n+i, t, b[i]);
        Insert(t, s+n+i, 0);
    }
    for(int i = 1; i <= n; i++){
        Insert(s+i, s+n+i, INF);
        Insert(s+n+i, s+i, 0);
    }

    int x, y;
    for(int i = 1; i <= m; ++i){
        scanf("%d%d", &x, &y);
        Insert(s+x, s+n+y, INF);
        Insert(s+n+y, s+x, 0);
        Insert(s+y, s+n+x, INF);
        Insert(s+n+x, s+y, 0);
    }

    if(sumA == sumB && sumB == Max_flow()){
        puts("YES");
        Print();
    }
    else  puts("NO");

    return 0;
}

总结

虽然没有参赛,但还是感觉自己好菜,整天想数据结构,应该更加重视链表,二分等基础知识,还有重视dp的训练,熟悉套路后dp题才好做。


高二&高一&初三模拟赛23 总结_第9张图片

温柔正确的人总是难以生存,因为这世界既不温柔,也不正确。

你可能感兴趣的:(模拟题总结)