CSP-S模拟7

学校体检:

内科:问,你是神经病吗;答,不是。下一个。

外科:问,你做过手术吗;答,没有。下一个。

基础检查:问,你身高体重是多少;答,……。下一个。

此处应有乌鸦飞过***

 

A. 序列问题

我连那个有假三维条件的dp式子都没想到,直接整了一个二维的玩意?!f[i][j]表示在B里填到了第i个数,这个数是A里的第j个,于是f[i][j] = max f[i-1][k]+[a[k]==i].可以加个滚动数组,不过还是50pts.

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1002;
const int mod = 1e9 + 7;

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

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

int main()
{
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++)
    {
        a[i] = read();
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=i; j<=n; j++)
        {
            int t = a[j]==i ? 1 : 0;
            for(int k=i-1; k<j; k++)
            {
                f[i][j] = max(f[i][j], f[i-1][k]+t);
                ans = max(ans, f[i][j]);
            }
        }
    }
    printf("%d\n", ans);
    
    return 0;
}

我本来也以为题解假了,不过题解它是对的,跳出来为题解伸个冤:

状态f[i]表示处理A中1~i位置的数,并且处理后的序列以ai结尾的最优答案,因为它是最优答案,所以ai一定对应了B里的某个位置而不是无效的值,无效的值填空的意义只是让后面的值有效。那么只有ai<=i的时候f[i]是合法的。

考虑一个j能转移到i必须满足这些条件:j < i    aj < ai    ai - aj <= i - j  就是j原来也在B中某个可以对应的位置上,B要往后加,a一定会更大,中间可以用无效的值来填空,但中间空不能太大,把i~j之间的数都用来填空是差值的上限。

这才是真的暴力转移50分,我那个连暴力dp都算不上555  f[i] = max f[j] + 1,j满足条件。

于是你把a[]sort了一下,求了个不下降子序列,WA 0.然后开始抱怨题解假了?

可是题解说了a是要求严格递增的呀!!!sort又没有去重!!

于是你想到了把重复的a设为最大值,让它不可能和上一个同等的值,但是这样显然不对,因为不一定最终会用到这一堆ai里的哪一个。

正确的做法应该是把所有的相等的ai看成一个组,分组转移。

code


//这种求最长不下降子序列的方法似乎比老师课件上的原版更好背
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e5 + 7;
const int mod = 1e9 + 7;

int n, a[maxn], tot, f[maxn], low[maxn], ans;

struct node 
{
    int a, b;
    bool operator < (const node &T) const 
    {
        return a < T.a;
    }
}e[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;
}

int main()
{
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++)
    {
        a[i] = read();
        if(a[i] <= i) {e[++tot].a = a[i], e[tot].b = i-a[i];}
    }
    for(int i=1; i<=tot; i++) low[i] = mod;
    sort(e+1, e+1+tot);
    //f[j]表示以j为结尾的最长不下降子序列的长度
    //low[i]表示最长不下降子序列的长度是i要求的关键字最小值
    //长度增加,对关键字的要求单调不下降
    for(int i=1; i<=tot; i++)//右端点也被统计了,i++不要扔
    {
        int l = i;
        while(i<tot && e[i].a==e[i+1].a) i++;
        int r = i;
        //因为要求a严格递增
        for(int j=l; j<=r; j++)
        {
            f[j] = upper_bound(low+1, low+1+tot, e[j].b)-1-low+1;
            //由小于等于当前关键字的最大长度转移
        }
        for(int j=l; j<=r; j++)
        {
            //转移完了把答案接在后面
            low[f[j]] = min(e[j].b, low[f[j]]);
        }
    }
    for(int i=1; i<=tot; i++)
    {
        ans = max(ans, f[i]);
    }
    printf("%d\n", ans);
    
    return 0;
}

然后还有一种树状数组的做法,也需要看出来“假三维偏序”:

为了让ai和i-ai都是有序的,可以先把ai排序,再按照i-ai的顺序来查找,使ai按顺序有效,也就是循环枚举的顺序变了!

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e5 + 2;
const int mod = 1e9 + 7;

int n, a[maxn], cnt;

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 node 
{
    int x, y;
    bool operator < (const node &T) const 
    {
        return y == T.y ? x < T.x : y < T.y;
    }
}d[maxn];

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

int main()
{
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) a[i] = read();
    for(int i=1; i<=n; i++) 
    {
        if(a[i] <= i) {d[++cnt].x = a[i]; d[cnt].y = i-a[i];}
        //提前把a[i]>i的不合法情况去掉
        //它不能当f[i]的结尾,所以不能参与转移
    }
    sort(d+1, d+cnt+1);
    int p = 1, ans = 0;
    for(int i=0; i<=n; i++)//枚举i-a[i]所有可能的结果,每一次增长都有上界
    //就使得更新顺序按i-a[i]有序
    {
        while(p <= cnt && d[p].y <= i)//处理后以a[p]结尾
        {
            int nans = t.query(d[p].x-1)+1;//a严格递增
            ans = max(ans, nans);
            t.add(d[p].x, nans);
            p++;
        }
    }
    printf("%d\n", ans);
    
    return 0;
}

 

B. 钱仓

Step1: 暴力枚举

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;

int n, c[maxn], p[maxn];
ll ans = 1e17;;

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

inline bool check(int l, int r)
{
    for(int i=l; i<=r; i++)
    {
        if(p[i] != 1) return 0;
    }
    return 1;
}

int main()
{
    freopen("barn.in", "r", stdin);
    freopen("barn.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) c[i] = read();
    for(int i=1; i<=n; i++) c[i+n] = c[i];
    for(int i=1; i<=n; i++)
    {
        if(!c[i]) continue;
        int j = i+n-1;
        ll res = 0;
        if(c[j] > 1) continue;
        for(int k=i; k<=j; k++)
        {
            p[k] = c[k];
        }
        for(int k=j; k>=i; k--)
        {
            if(!p[k])
            {
                for(int s=k-1; s>=i; s--)
                {
                    if(!p[s]) continue;
                    p[s]--; p[k]++;
                    res += 1ll*(k-s)*(k-s);
                    break;
                }
            }
        }
        if(!check(i, j)) continue;
        ans = min(ans, res);
    }
    printf("%lld\n", ans);
    
    return 0;
}

Chen_jr一语点醒梦中Cat——从不同的节点开始得到的答案相同,因为各个起点其实独立处理了它到下一个起点的区间,所以找到一组解就结束了。

但只知道这个并不能拯救Cat原本的TTTTLE解法,她把每种情况都算了出来,最后看看是不是每个值都变成了1,以此来检验起点的合法性。

351238指出了不T的好方法,那就是后缀和不能大于后缀的大小,否则它一定需要往后运输而后面又盛不下这么多,于是Cat加了一堆特判,其中只有上面的那条后缀和是有用的。懒得改了。

加特判判完之后不要忘了break!否则和不判断没有毛线区别&**

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;

int n, c[maxn], p[maxn];
ll ans = 1e17;;

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

inline bool check(int l, int r)
{
    for(int i=l; i<=r; i++)
    {
        if(p[i] != 1) return 0;
    }
    return 1;
}

int main()
{
    freopen("barn.in", "r", stdin);
    freopen("barn.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) c[i] = read();
    for(int i=1; i<=n; i++) c[i+n] = c[i];
    for(int i=1; i<=n; i++)
    {
        bool f = 0;
        if(!c[i]) continue;
        int j = i+n-1;
        ll res = 0;
        if(c[j] > 1) continue;
        ll cnt = 0, sum = 0;
        for(int k=j; k>=i; k--)
        {
            cnt++;
            sum += c[k];
            if(sum > cnt) 
            {
                f = 1; break;
            }
        }
        if(f) continue;
        for(int k=i; k<=j; k++)
        {
            p[k] = c[k];
        }
        for(int k=j; k>=i; k--)
        {
            if(!p[k])
            {
                for(int s=k-1; s>=i; s--)
                {
                    if(!p[s]) continue;
                    p[s]--; p[k]++;
                    res += 1ll*(k-s)*(k-s);
                    break;
                }
            }
            if(p[k] > 1) 
            {
                f = 1; break;
            }
            if(f) break;
        }
        if(f) continue;
        if(!check(i, j)) continue;
        ans = min(ans, res);
        break;
    }
    printf("%lld\n", ans);
    
    return 0;
}

 

C. 自然数

第一印象:题目这个定义,为什么我联想到了SG函数!?难道是数论题!?

第二印象:所有区间问题?我想到了线段树和CDQ分治两种套路形式,but不会实现……

我的暴力枚举好像和别人的都不太一样?不去记录取过哪些数,用一个set上二分找到哪些数还没取,比n^2还慢……

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 3002;
const int mod = 1e9 + 7;

int n, a[maxn];
ll ans;
set<int> s;
set<int>::iterator it;

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

int main()
{
    freopen("mex.in", "r", stdin);
    freopen("mex.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) a[i] = read();
    for(int i=0; i<=n; i++)
    {
        s.insert(i);
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=i; j<=n; j++)
        {
            it = s.find(a[j]);
            if(it != s.end()) s.erase(it);
            int y = *s.lower_bound(0);
            //printf("mex(%d, %d) = %d\n", i, j, y);
            ans += y;
        }
        for(int j=i; j<=n; j++)
        {
            if(a[j] <= n) s.insert(a[j]);
        }
    }
    printf("%lld\n", ans);
    
    return 0;
}

鹤题解,尽管我自己抄了一遍,但它还是被查重了***谁让你不改变量名!我偏不改!!

考虑每次删去一个点的贡献,初始令l=1线段树第x个叶子维护[l,x]的mex,考虑每次移动l到l+1的影响,它只会影响下一个与l数字相同的位置之前,在该区间内,所有mex都变成原来的mex和删去的l位置的数的最小值。发现mex有单调性,那么在线段树对应区间二分,然后区间覆盖即可。

需要维护区间最小值,区间总和,实现区间覆盖,区间查询。

——直接引用自Chen_jr

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;

int nxt[maxn], pos[maxn], n, a[maxn], me[maxn];
bool vis[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 tree 
{
    struct node 
    {
        int tag, mi;
        ll sum;
    }t[maxn<<2];
    void pushup(int x)
    {
        t[x].sum = t[x<<1].sum + t[x<<1|1].sum;
        t[x].mi = min(t[x<<1].mi, t[x<<1|1].mi);
    }
    void build(int x, int l, int r)
    {
        t[x].tag = -1;
        if(l == r)
        {
            t[x].mi = t[x].sum = me[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(x<<1, l, mid);
        build(x<<1|1, mid+1, r);
        pushup(x);
    }
    void upd(int x, int l, int r, int val)
    {
        t[x].mi = val;
        t[x].sum = 1ll*(r-l+1)*val;
        t[x].tag = val;
    }
    void pushdown(int x, int l, int r)
    {
        int mid = (l + r) >> 1;
        upd(x<<1, l, mid, t[x].tag);
        upd(x<<1|1, mid+1, r, t[x].tag);
        t[x].tag = -1;
    }
    void modify(int x, int l, int r, int L, int R, int val)
    {
        int mid = (l + r) >> 1;
        if(L <= l && r <= R)
        {
            if(t[x].mi > val)
            {
                upd(x, l, r, val);
                return;
            }
            if(l == r) return;
            if(t[x].tag != -1) pushdown(x, l, r);
            if(t[x<<1|1].mi > val)
            {
                upd(x<<1|1, mid+1, r, val);
                modify(x<<1, l, mid, L, R, val);
            }
            else 
            {
                modify(x<<1|1, mid+1, r, L, R, val);
            }
            pushup(x);
            return;
        }
        if(t[x].tag != -1) pushdown(x, l, r);
        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);
    }
    ll query(int x, int l, int r, int L, int R)
    {
        if(L <= l && r <= R) return t[x].sum;
        if(t[x].tag != -1) pushdown(x, l, r);
        int mid = (l + r) >> 1; ll ans = 0;
        if(L <= mid) ans += query(x<<1, l, mid, L, R);
        if(R > mid) ans += query(x<<1|1, mid+1, r, L, R);
        return ans;
    }
}t;

int main()
{
    freopen("mex.in", "r", stdin);
    freopen("mex.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++) a[i] = read();
    int mex = 0;
    for(int i=1; i<=n; i++)
    {
        if(a[i] < n) 
        {
            vis[a[i]] = 1;
            while(vis[mex]) mex++;
        }
        me[i] = mex;
    }
    t.build(1, 1, n);
    for(int i=n; i>0; i--)
    {
        if(a[i] < n)
        {
            nxt[i] = pos[a[i]];
            pos[a[i]] = i;
        }
    }
    ll ans = 0;
    for(int l=1; l<=n; l++)
    {
        ans += t.query(1, 1, n, l, n);
        t.modify(1, 1, n, l, nxt[l]?nxt[l]-1:n, a[l]);
    }
    printf("%lld\n", ans);
    
    return 0;
}

 

D.环路

∑i=1~k Ai = ∑i=1~k/2 Ai + Ai=1~k/2 Ai * A^k/2 + [k&1] A^k

不得不说我的数学公式写的实在是太丑啦!亲爱的读者还是去mod Chen_jr吧!!

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

using namespace std;

typedef long long ll;
const int maxn = 105;
const int mod = 1e9 + 7;

int n, k, m;
/*
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;
}*/

inline int read()
{
	int x = 0; 
    char c = getchar();
	while(c < '0' || c > '9')
    {
		if(c == 'Y')return 1;
		if(c == 'N')return 0;
		c = getchar();
	}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}

struct matrix 
{
    int a[maxn][maxn];
    void clear()
    {
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                a[i][j] = 0;
            }
        }
    }
    matrix() {memset(a, 0, sizeof(a));}
    friend matrix operator * (const matrix &x, const matrix &y)
    {
        matrix c;
        for(int i=0; i<n; i++)
        {
            for(int k=0; k<n; k++)
            {
                for(int j=0; j<n; j++)
                {
                    c.a[i][j] = (c.a[i][j]+1ll*x.a[i][k]*y.a[k][j])%m;
                }
            }
        }
        return c;
    }
    friend matrix operator + (const matrix &x, const matrix &y)
    {
        matrix c;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                c.a[i][j] = (x.a[i][j] + y.a[i][j]) % m;
            }
        }
        return c;
    }
}mp;
int ans;
typedef pair<matrix, matrix> pmm;

pmm solve(int k)
{
    if(k == 1) return pmm(mp, mp);
    pmm now = solve(k/2);
    matrix x = now.first, y = now.second;
    matrix ans = x * now.second;
    now.second = now.second * now.second;
    if(k & 1)
    {
        now.second = now.second * mp;
        ans = ans + now.second;
    }
    return pmm(ans+x, now.second);
}

int main()
{
    freopen("tour.in", "r", stdin);
    freopen("tour.out", "w", stdout);
    
    n = read();
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            mp.a[i][j] = read();
        }
    }
    k = read(); m = read();
    matrix ma = solve(k-1).first;
    for(int i=0; i<n; i++)
    {
        ans = (ans + ma.a[i][i]) % m;
    }
    printf("%d\n", ans);
    
    return 0;
}
posted @ 2022-09-20 20:10  Catherine_leah  阅读(25)  评论(0编辑  收藏  举报
/* */