2022NOIPA层联测23 今天的所有题都好评!!

信息学考就要到了,于是去补课

我一个OIer花了半节课都没有通过编译……

A. zzy 的金牌

所求即为b1……bn的数量,满足

  • b[i]+a[i] >= b[i-1]+a[i-1]
  • ∑b = k

关于差分数组和后缀和,放个code我溜了 qwq

code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;

int n, k, a[maxn], ans, f[maxn][maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void add(int &x, int y) 
{
    x += y; x = x >= mod ? x - mod : x;
    x = x < 0 ? x + mod : x;
}

int main()
{
    freopen("orzzy.in", "r", stdin);
    freopen("orzzy.out", "w", stdout);

    n = read(); k = read();
    for(int i=1; i<=n; i++) a[i] = read();
    sort(a+1, a+1+n);
    f[0][0][0] = 1; f[0][1][0] = -1;
    for(int i=0; i<=n; i++)
    {
        for(int j=1; j<=k; j++)
        {
            for(int p=0; p<=k; p++)//前i-1个数的和
            {
                add(f[i][j][p], f[i][j-1][p]);
            }
        }
        for(int j=0; j<=k; j++)
        {
            for(int p=0; p<=k-j; p++)
            {
                int l = max(0,j+a[i]-a[i+1]), r = k-p-j;
                if(l > r) continue;
                add(f[i+1][l][p+j], f[i][j][p]); add(f[i+1][r+1][p+j], -f[i][j][p]);
            }
        }
    }
    for(int i=0; i<=k; i++)
    {
        add(ans, f[n][i][k-i]);
    }
    printf("%d\n", ans);

    return 0;
}
code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;

int n, k, a[maxn], ans, f[maxn][maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void add(int &x, int y) 
{
    x += y; x = x >= mod ? x - mod : x;
    x = x < 0 ? x + mod : x;
}
//我还在鹤……
int main()
{
    freopen("orzzy.in", "r", stdin);
    freopen("orzzy.out", "w", stdout);
    
    n = read(); k = read();
    for(int i=1; i<=n; i++) a[i] = read();
    sort(a+1, a+1+n);
    f[0][0][k] = 1;
    for(int i=0; i<=n; i++)
    {
        for(int j=0; j<=k; j++)//bi
        {
            int l = max(a[i]+j-a[i+1], 0);//i+1最少要填l,后面的都可以
            for(int r=l; r<=k; r++)//当前的余量从l开始的后缀和,至少要有l的余量才能把i+1填进去
            {
                if(f[i][j][r]) add(f[i+1][l][r-l], f[i][j][r]);
                //现在i是后缀和,而i+1只有当前值
            }
        }
        for(int j=0; j<=k; j++)//bi+1
        {
            for(int r=1; r<=k; r++)
            {
                add(f[i+1][j+1][r-1], f[i+1][j][r]);
            }
        }
    }
    for(int i=0; i<=k; i++) add(ans, f[n][i][0]);
    printf("%d\n", ans);

    return 0;
}
code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;

int n, k, a[maxn], ans, f[maxn][maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void add(int &x, int y) 
{
    x += y; x = x >= mod ? x - mod : x;
    x = x < 0 ? x + mod : x;
}
//我还在鹤……
//既然都鹤了那么也就不介意再多鹤两版。。
int main()
{
    freopen("orzzy.in", "r", stdin);
    freopen("orzzy.out", "w", stdout);
    
    n = read(); k = read();
    for(int i=1; i<=n; i++) a[i] = read();
    sort(a+1, a+1+n);
    f[0][0][0] = 1; f[0][1][0] = -1;
    for(int i=0; i<=n; i++)
    {
        for(int s=0; s<=k; s++)
        {
            for(int j=1; j<=k; j++)
            {
                add(f[i][j][s], f[i][j-1][s]);
            }
            for(int j=0; j<=k-s; j++)
            {
                int l = max(a[i]+j-a[i+1],0), r = k-s-j;
                if(l <= r) add(f[i+1][l][s+j], f[i][j][s]), add(f[i+1][r+1][s+j], -f[i][j][s]);
            }
        }
    }
    for(int i=0; i<=k; i++) add(ans, f[n][i][k-i]);
    printf("%d\n", ans);

    return 0;
}

 

B. 口粮输送

就连树上的判定都不需要dp!!在树上一条边至多被经过一次,也就是一棵∑(bi-ai)>=∑wi的树一定存在合法方案。如果一棵∑(bi-ai)<∑wi的树存在合法方案,则必然存在一条边在这个方案中没有被经过,可以删去这条边。

合法输送方案的导出子图的,每个连通块C一定有∑bi<=∑ai-∑e∈CwC。将C换成它的最小生成树,能解决∑(bi-ai)>=∑e∈CwC的所有问题。E一定是某个最小生成森林。

code
 /*
感觉今天的考题适合用来珍藏qwq
*/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 17;
const int N = 290;

int T, n, m, a[maxn], b[maxn], fa[maxn];
bool f[65539], in[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct edge 
{
    int u, v, w;
    bool operator < (const edge &T) const
    {
        return w < T.w;
    }
}e[maxn*maxn];

int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
bool merge(int x, int y) {x = find(x), y = find(y); if(x == y) return false; fa[y] = x; return true;}

bool mst(int s)
{
    int smt = 0, snt = 0, cnt = 0;
    for(int i=1; i<=n; i++) 
    {
        if(s & (1<<i-1)) snt += a[i] - b[i], in[i] = true, cnt++;
        else in[i] = false;
    }
    for(int i=1; i<=n; i++) fa[i] = i;
    for(int i=1; i<=m; i++)
    {
        if(in[e[i].u] && in[e[i].v] && merge(e[i].u, e[i].v))
        {
            smt += e[i].w; cnt--;
        }
    }
    return cnt == 1 && snt - smt >= 0;//这里只考虑树,森林之后再说
}

void solve()
{
    n = read(); m = read();
    for(int i=1; i<=m; i++)
    {
        int u = read(), v = read(), w = read();
        e[i] = {u, v, w};
    }
    sort(e+1, e+1+m);
    for(int i=1; i<=n; i++) a[i] = read(), b[i] = read();
    for(int i=1; i<(1<<n); i++) f[i] = mst(i);
    for(int i=1; i<(1<<n); i++) if(f[i] == false)
    {
        for(int j=(i-1)&i; j&&!f[i]; j=(j-1)&i)
        {
            f[i] |= f[j] & f[i^j];
        }
    }
    if(f[(1<<n)-1]) printf("Yes\n");
    else printf("No\n");
}

int main()
{
    freopen("trans.in", "r", stdin);
    freopen("trans.out", "w", stdout);
    
    T = read();
    while(T--)
    {
        solve();
    }

    return 0;
}

 

C. 作弊

为了防止改不完题,这个神奇的东西我一定要现在就写!

%%%Chen_jr  一看就知道我又鹤了

设s(l, r)表示在[l, r]之间作一次弊的最大收益,这个东西居然可以优化!!转移方程是f[i] = max(f[i], f[j-1]+s(j, i)),发现每一个时刻只有右端点是i的s是有用的,线段树的一部分就是右端点为i时,左端点为(线段树下标)的s的大小,同时还把f[j-1]记录到了f[j]上,这样就可以让答案都在一个点上维护线段树的区间最值了!

现在就需要维护右端点移动时s的变化。(鹤一下)考虑分开计算每个人的贡献,i对s(i, j)产生贡献当且仅当mx(l, r)属于[li, ri],将其拆成max(mx(l, i), mx(i, r))属于[li, ri],也就是mx(l,  i),mx(i, r)都要<=ri,且其中至少一个>=li。

对于每个i预处理出mx(l, i),mx(i, r)属于[li, ri]的l, r的范围,记为[lri, lli], [rli, rri](因为mx从i向左递增,从i向右递增),第一个l/r表示是谁的范围,第二个l/r表示满足哪个条件(>=li还是<=ri)。具体实现的时候找的是(以l的范围为例)满足a[j]>=l[i]的i左边的最大的j和满足a[k]>r[i]的i左边的最大的k,它+1才是标准的范围,但是恰好可以在线段树上消去贡献时作为起点。

预处理可以用树状数组,下标是值域,val是位置,因为要找值更大的,可以直接往后找,也可以把树状数组翻转过来让a更大的在左边,向左要找val最小的,向右要找val最大的,原来的函数可以直接用,把权值和答案都变成相反的就好,这样负负得正。

当右端点属于[i, ri]时,左端点在[lli, lri]内有贡献;属于[rli, rri]时,左端点在[lli, i[内有贡献,注意[lri, lli]内的贡献不需要重复统计所以从lli+1开始;大于rri时,没有贡献。第一层循环的i既表示讨论到的位置,又表示当前的右端点,一开始先把i更新为f[i-1]修改之后它就变成i了,f[i-1]不是f[i]的初值因为s(i, i)可能不是0。记录以i为转折点的x,在适当的时候去掉x的贡献。

code
#include <bits/stdc++.h>

using namespace std;

//typedef long long ll;你猜为什么过不了编译
const int maxn = 1e5 + 5;

int f[maxn], a[maxn], n, l[maxn], r[maxn];
int ll[maxn], lr[maxn], rl[maxn], rr[maxn];
vector<int> RL[maxn], RR[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct BIT 
{
    int t[maxn];
    int lowbit(int x) {return x & -x;}
    void modify(int x, int val) {x = n - x + 1; while(x <= n) {t[x] = max(t[x], val); x += lowbit(x);}}
    int query(int x) {x = n - x + 1; int ans = 0; while(x) {ans = max(ans, t[x]); x -= lowbit(x);} return ans;}
    void clear() {for(int i=1; i<=n; i++) t[i] = 0;}
}T;

struct seg 
{
    struct node 
    {
        int val, tag;
    }t[maxn<<2];
    void pushdown(int x)
    {
        int ls = x<<1, rs = x<<1|1;
        t[ls].val += t[x].tag;
        t[rs].val += t[x].tag;
        t[ls].tag += t[x].tag;
        t[rs].tag += t[x].tag;
        t[x].tag = 0;
    }
    void pushup(int x)
    {
        t[x].val = max(t[x<<1].val, t[x<<1|1].val);
    }
    void modify(int x, int l, int r, int L, int R, int val)
    {
        if(L <= l && r <= R)
        {
            t[x].val += val; t[x].tag += val;
            return;
        }
        if(t[x].tag) pushdown(x);
        int mid = (l + r) >> 1;
        if(L <= mid) modify(x<<1, l, mid, L, R, val);
        if(R > mid) modify(x<<1|1, mid+1, r, L, R, val);
        pushup(x);
    }
    int query(int x, int l, int r, int L, int R)
    {
        if(L <= l && r <= R) return t[x].val;
        if(t[x].tag) pushdown(x);
        int mid = (l + r) >> 1, ans = 0;
        if(L <= mid) ans = max(ans, query(x<<1, l, mid, L, R));
        if(R > mid) ans = max(ans, query(x<<1|1, mid+1, r, L, R));
        return ans;
    }
}t;

int main()
{
    freopen("cheat.in", "r", stdin);
    freopen("cheat.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) a[i] = read();
    for(int i=1; i<=n; i++) l[i] = read(), r[i] = read();
    for(int i=1; i<=n; i++)
    {
        T.modify(a[i], i); ll[i] = T.query(l[i]), lr[i] = T.query(r[i]+1);
    }
    T.clear();
    for(int i=n; i>=1; i--)
    {
        T.modify(a[i], n-i+1); rl[i] = n-T.query(l[i])+1, rr[i] = n-T.query(r[i]+1)+1;
    }
    for(int i=1; i<=n; i++)
    {
        if(rl[i]) RL[rl[i]].push_back(i);
        if(rr[i]) RR[rr[i]].push_back(i);
    }
    for(int i=1; i<=n; i++)
    {
        t.modify(1, 1, n, i, i, t.query(1, 1, n, 1, n));
        if(ll[i]) t.modify(1, 1, n, 1, ll[i], 1);
        if(lr[i]) t.modify(1, 1, n, 1, lr[i], -1);
        for(int x : RL[i]) if(ll[x] < x) t.modify(1, 1, n, ll[x]+1, x, 1);
        for(int x : RR[i]) if(lr[x] < x) t.modify(1, 1, n, lr[x]+1, x, -1);
    }
    printf("%d\n", t.query(1, 1, n, 1, n));

    return 0;
}

 

posted @ 2022-11-08 19:18  Catherine_leah  阅读(30)  评论(0编辑  收藏  举报
/* */