CSP-S模拟9

It's not until you fall that you fly…

 

A. 最长上升子序列

有一篇题解的图很形象的描绘了假贪心和正解的差别:https://blog.csdn.net/weixin_45911397/article/details/119860304

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

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const long double INF = 1e16;

int a[maxn], n, k, c[maxn], cnt;
bool v[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()
{
    n = read(); k = read();
    for(int i=1; i<=k; i++)
    {
        a[i] = read(); v[a[i]] = 1;
    }
    for(int i=1; i<=n; i++)
    {
        if(v[i]) continue;
        else c[++cnt] = i;
    }
    int num = 1;
    for(int i=1; i<k; i++)
    {
        printf("%d ", a[i]);
        if(num<=cnt && c[num]<a[i]) printf("%d ", c[num++]);
    }
    while(num<=cnt && c[cnt]>a[k]) printf("%d ", c[cnt--]);
    printf("%d ", a[k]);
    while(num<=cnt) printf("%d ", c[cnt--]);
    printf("\n");
    
    return 0;
}

 

B. 独特序列

Cat是怎么错的:我想到了设f[i]表示A中考虑到i并且必须选上i的方案数,然后找到没有后继的i更新答案,但是关于转移(从j到i)我只想到了i到j这段区间中不能出现a[i],其实a[j]也不能出现,由于没有发现a[j]的问题,我发现可以直接累计前缀和相减,而且还不包括pre[i],我把它们单独拿出来算了,因为出现多次的数只有全部出现才可以合法,所以后面加上答案2^只出现一次的数的个数,但是多次出现的数虽然不能作为答案但是可以参与转移,不能遇到重复就跳过。

当Cat调了一个上午发现i到j出现a[j]不对的时候,距离考试结束还有20 min!?于是就急忙的写了个暴力,T3T4还没来得及仔细审题***

code

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;

int n, a[maxn], d[maxn], ans, Max;
int p[maxn], l[maxn], r[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;
}

bool check(int num)
{
    for(int i=1; i<=n; i++)
    {
        if(num & (1<<(i-1))) d[i] = 1;
        else d[i] = 0;
    }
    //for(int i=1; i<=n; i++) printf("%d ", d[i]);
    //printf("\n");
    int las = 0;
    for(int i=1; i<=n; i++)
    {
        if(!d[i]) continue;
        if(l[i] > las) return 0;
        las = i;
        /*int j = i-1;
        while(!d[j] && j>0)
        {
            if(a[j] == a[i]) return 0;
            j--;
        }
        j = i+1;
        while(!d[j] && j<=n)
        {
            if(a[j] == a[i]) return 0;
            j++;
        }*/
    }
    las = n+1;
    for(int i=n; i>=1; i--)
    {
        if(!d[i]) continue;
        if(r[i] && r[i] < las) return 0;
        las = i;
    }
    return 1;
}

int main()
{
    n = read(); 
    for(int i=1; i<=n; i++) a[i] = read();
    //
    for(int i=1; i<=n; i++)
    {
        l[i] = p[a[i]]; p[a[i]] = i;
    }
    for(int i=1; i<=n; i++) p[i] = 0;
    for(int i=n; i>=1; i--)
    {
        r[i] = p[a[i]]; p[a[i]] = i;
    }
    Max = 1 << n;
    for(int i=1; i<Max; i++)
    {
        if(check(i)) ans++;
    }
    ans %= mod;
    printf("%d\n", ans);
    
    return 0;
}

所以正解就是要在删除不合法的a[j]在j~i(开区间)出现过的不合法的f[j]的情况下保证时间,暴力判断的话TLE 45,可以用树状数组维护前缀和并进行单点修改。初始化f[0]=1在树状数组上的体现就是把下标整体右移,所以query(i)-query(pre[i])想表达的其实是query(i-1)-query(pre[i]-1),重复的a[i]可以恰好出现在转移点上,f[pre[i]]是计算在内的。

code

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;

int n, a[maxn], nxt[maxn], pre[maxn], las[maxn];
ll c[maxn], ans, f[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, ll val)
{
    while(x <= n)
    {
        c[x] += val;
        x += (x&-x);
    }
}

ll query(int x)
{
    ll ans = 0;
    while(x)
    {
        ans += c[x];
        x -= (x&-x);
    }
    return ans;
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        a[i] = read();
        if(las[a[i]])
        {
            nxt[las[a[i]]] = i; pre[i] = las[a[i]];
        }
        las[a[i]] = i;
    }
    add(1, 1);
    for(int i=1; i<=n; i++)
    {
        f[i] = (query(i)-query(pre[i]))%mod;
        if(pre[i]) add(pre[i]+1, -f[pre[i]]);
        if(!nxt[i]) ans = (ans+f[i])%mod;
        add(i+1, f[i]);
    }
    printf("%lld\n", ans);
    
    return 0;
}

 

C. 最大GCD

当我看到数据范围,我以为暴力不可能有分还不如去调T2,于是直到考试结束我还在写T2……

结果最简单的暴力TLE 45,二分乱搞好像也很优秀,小于枚举的gcd的a预处理前缀和剩下的暴力计算TLE 60……我也不知道我应不应该后悔去搞了一上午的T2呢***

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

using namespace std;

typedef long long ll;
const int maxn = 3e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;

ll n, k, gcc, a[maxn], mx, ans, tot;

inline ll read()
{
    ll 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;
}

bool check(int num)
{
    ll ned = 0;
    //printf("check(%d)\n", num);
    for(int i=1; i<=n; i++)
    {
        ll w = a[i] % num;
        //printf("w = %lld\n", w);
        if(w)
        {
            ned += num - w;
            if(ned > k) return 0;
        }
    }
    return 1;
}

int main()
{
    n = read(); k = read();
    for(int i=1; i<=n; i++) 
    {
        a[i] = read(); mx = max(a[i], mx);
    }
    for(int i=1; i<=n; i++)
    {
        tot += mx - a[i];
    }
    if(k >= tot)
    {
        k -= tot;
        ans = mx + k / n;
        printf("%lld\n", ans);
        exit(0);
    }
    for(int i=mx; i>=1; i--)
    {
        if(check(i)) 
        {
            printf("%d\n", i);
            break;
        }
    }
    
    return 0;
}

https://blog.csdn.net/weixin_45483201/article/details/120442178

判断这个东西合不合法,最主要的优化在于分段求和,由于特定区间向上取整的值是一样的,总数不随枚举的gcd而变化,就可以先预处理再分段枚举了。

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

using namespace std;

typedef long long ll;
const int maxn = 3e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;

int a[maxn], cnt[maxn], n, Max;
ll sum[maxn], k, s, ans;

inline ll read()
{
    ll 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()
{
    n = read(); k = read();
    for(int i=1; i<=n; i++)
    {
        a[i] = read();
        s += a[i]; cnt[a[i]]++; sum[a[i]] += a[i];
        Max = max(Max, a[i]);
    }
    if(1ll*Max*n-s <= k)
    {
        ans = (k+s)/n;
        printf("%lld\n", ans); exit(0);
    }
    for(int i=1; i<=Max; i++)
    {
        cnt[i] += cnt[i-1];
        sum[i] += sum[i-1];
    }
    ans = 1;
    for(int i=2; i<Max; i++)
    {
        ll res = 0;
        for(int j=i; j<=Max; j+=i)
        {
            res += 1ll*(cnt[j]-cnt[j-i])*j-(sum[j]-sum[j-i]);
        }
        if(Max % i)
        {
            int l = Max/i*i, r = Max;
            res += 1ll*(cnt[r]-cnt[l])*(Max/i+1)*i-(sum[r]-sum[l]);
        }
        if(res <= k) ans = i;
    }
    printf("%lld\n", ans);
    
    return 0;
}

 

D. 连续子段

我一开始打算把每次交换后的状态扔进去bfs,结果用map判重过不了编译原来是定义结构体排序的问题,我又想把它们先转string再bfs,结果读入数据和判断结束的依据都是个大问题,把数字直接放进sting居然输出一堆乱码……于是我放弃了***回去写T2了

https://www.cnblogs.com/gtm1514/p/16719577.html

鹤了%%%gtm1514的题解感觉良好

尽管粘了链接还是要抄一遍:

设f[i][j]表示扫描到i,找到的1~k的数的集合为j的最小步数,有两种转移:

1.如果扫到的数没在集合里就把它加进来,需要的步数是集合和这个数组成的逆序对数,也就是j中比a大的数的个数,也就是j中高于第a位的为1的位的个数。

2.不加入扫到的数,它可能是新的也可能不是,不过它都是空位,最终连续的k个数里是不能有空位的,所以要么是选上的数向右平移,要么是没选上的数向左平移,可以预处理最小值。

code


#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5002;
const int N = 2e5 + 3;
const int INF = 0x3f3f3f3f;

int n, k, f[(1<<16)+5], cnt[(1<<16)+5], t[(1<<16)+5];

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()
{
    n = read(); k = read();
    for(int j=1; j<(1<<k); j++) 
    {
        cnt[j] = cnt[j>>1] + (j&1);
        t[j] = min(cnt[j], k-cnt[j]);
    }
    for(int j=1; j<(1<<k); j++) f[j] = INF;
    for(int i=1; i<=n; i++)
    {
        int a = read()-1;
        for(int j=(1<<k)-1; j>=0; j--)
        {
            if(f[j] != INF)
            {
                if(!(j&(1<<a))) f[j|(1<<a)] = min(f[j|(1<<a)], f[j]+cnt[j>>a]);
                f[j] += t[j];
            }
        }
    }
    printf("%d\n", f[(1<<k)-1]);
    
    return 0;
}
posted @ 2022-09-22 17:52  Catherine_leah  阅读(20)  评论(0编辑  收藏  举报
/* */