寒假训练第四周

寒假训练第四周

第一天

cf排位赛

C. Constructive Problem

大意:
给定n,得到0~n-1的升序列,要求构造一个序列,使第i个原序列出现的次数等于构造序列第i个的值。

思路:
手玩找规律

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int mod=1e9+7;
int main()
{
    int n;
    cin>>n;
    if(n<=3||n==6)cout<<-1;
    else if(n==4)cout<<"1 2 1 0";
    else if(n==5)cout<<"2 1 2 0 0";
    else if(n==7)cout<<"3 2 1 1 0 0 0 ";
    else{
        cout<<n-4<<' '<<"2 1";
        for(int i=1;i<=n-7;i++)cout<<" 0";
        cout<<" 1 0 0 0";
    }
    return 0;
}

G. Generate 7 Colors、

大意:
给定0~6的固定序列,再给出数字需出现的次数,问需要几段固定序列(固定序列必须从头取,可取任意长度)

思路:
必须达到固定序列的循环节才可以合并,否则都得重新再取,
因此将循环节开头数字出现次数减去结尾的即可,
注意至少为1.

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int mod=1e9+7;
ll a[10];
int main()
{
    int t;
    cin>>t;
    while(t--){
        for(int i=0;i<7;i++){
            scanf("%d",&a[i]);
        }
        bool flag= false;
        for(int i=0;i<6;i++){
            if(a[i]<a[i+1]){
                printf("-1\n");
                flag= true;
                break;
            }
        }
        if(!flag){
            printf("%lld\n",max(1ll,a[0]-a[6]));
        }
    }
    return 0;
}

E.quality

大意:
给定数组a,和子序列长度k,每次操作可让a中子序列都变成该序列中最小的值,问至少几次操作使数组中所有数相同。

思路:
易得最后数组中全为最小值,
从左向右考虑,碰到不是最小值时 ,从这个 数 开始,往右使用若 干段长度 k 的区间相扣,直到某一段长度 k 的区间内含有 最小值 时才停 止。 如果右边完全没有 最小值,则直接从左侧最近的 非最小值 开始往右以 k-1 为步 长扩展。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int mod=1e9+7;
ll a[10];
int main()
{
    int t;
    cin>>t;
    while(t--){
        for(int i=0;i<7;i++){
            scanf("%d",&a[i]);
        }
        bool flag= false;
        for(int i=0;i<6;i++){
            if(a[i]<a[i+1]){
                printf("-1\n");
                flag= true;
                break;
            }
        }
        if(!flag){
            printf("%lld\n",max(1ll,a[0]-a[6]));
        }
    }
    return 0;
}

K. Klee and Bomb

大意:
给定n个爆竹和各自的颜色,再给定m个连接关系,若互相链接的爆竹颜色相同则可以被互相引爆。自选爆炸点,最多可以选一个爆竹调换成任意颜色,问最多可以爆炸几个爆竹。

思路:
把可互相引爆的爆竹纳入一个加权并查集(此处是集合大小),
先考虑一个爆炸点,算出它的爆炸大小,再对于与它相连但颜色不同爆炸集,分别考虑把当前爆竹换成其他颜色可产生的爆炸大小,取最值。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int fa[N],c[N],sz[N];
vector<vector<int>> e(N);
int find(int x){
    if(x==fa[x])return x;
    return fa[x]=find(fa[x]);
}
void merge(int x,int y){
    x=find(x),y=find(y);
    if(x==y)return;
    fa[x]=y;
    sz[y]+=sz[x];
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
        fa[i]=i;
        sz[i]=1;
    }
    while(m--){
        int u,v;
        scanf("%d%d",&u,&v);
        if(c[u]==c[v]){
            merge(u,v);
        }
        else {
            e[u].push_back(v);
            e[v].push_back(u);
        }
    }
    int res=0;
    for(int u=1;u<=n;u++){
        map<int,int> M,vis;
        for(int v:e[u]){
            v=find(v);
            if(vis[v])continue;
            vis[v]=true;
            M[c[v]]+=sz[v];
        }
        res=max(res,sz[find(u)]);
        for(auto t:M){
            res=max(res,t.second+1);
        }
    }
    cout<<res<<endl;


    return 0;
}

第二天

记忆宏

Function

大意:
给定递归函数,不允许调用多次

思路:
记忆化,用数组存储答案,
记忆宏+三目运算符大幅减少代码。
记忆宏本质还是宏替换,只是宏中的变量可以被赋值,
注意三目运算符最外边要加上括号。

代码:


#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int M=4*N;
const int mod=1e9+7;
ll arr[25][25][25];
#define W(a,b,c) (arr[a][b][c]?arr[a][b][c]:arr[a][b][c]=w(a,b,c))
int w(ll a,ll b,ll c){
    if (a<=0||b<=0||c<=0) return 1;
    if(a>20||b>20||c>20) return W(20,20,20);
    if(a<b&&b<c)return W(a,b,c-1)+W(a,b-1,c-1)-W(a,b-1,c);
    else return W(a-1,b,c)+W(a-1,b-1,c)+W(a-1,b,c-1)-W(a-1,b-1,c-1);
}
int main(){
    while(1) {
        ll a, b, c;
        scanf("%lld%lld%lld", &a, &b, &c);
        if(a==-1&&b==-1&&c==-1)break;
        ll ans= w(a,b,c);
        printf("w(%lld, %lld, %lld) = %lld",a,b,c,ans);
        cout<<endl;
    }
    return 0;
}

加权并查集

银河英雄传说

大意:
两种操作,合并军队,与查询两个战舰间有多少支战舰

思路:
并查集权是集合大小

代码:


#include <bits/stdc++.h>using namespace std;
#define ll long long#define endl '\n'const int N=1e5+10;
const int mod=1e9+7;
int t,f[30005],value[30005],num[30005];
int find(int k){
    if(f[k]==k)return f[k];
    int fa=find(f[k]);
    value[k]+=value[f[k]];
    return f[k]=fa;
}
int main(){
    for(int i=1;i<=30000;i++){
        f[i]=i;num[i]=1;
    }
    cin>>t;
    while(t--){
        char c;int x,y;
        cin>>c>>x>>y;
        int f1=find(x),f2=find(y);
        if(c=='M'){
            f[f1]=f2;
            value[f1]+=num[f2];
            num[f2]+=num[f1];
        }else{
            if(f1!=f2)cout<<-1<<endl;
            else cout<<abs(value[x]-value[y])-1<<endl;
        }
    }
    return 0;
}

第三天

cf排位赛

J. Substring Inversion (Easy Version)

大意:
给定一个字符串,构造字串A,B,要求A字典序大于B,并且,B的首字符在A的后,求共有多少对。

思路:
字符串暴力,一个个枚举。
按从小到大排序,出现过的标记,后面的在首字符后加上标记数量。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int mod=1e9+7;
vector<pair<string,int>> v;
ll sum[410];
int main()
{
    int T;cin>>T;
    while(T--){
        v.clear();
        int n;cin>>n;
        string s;cin>>s;
        for(int i=0;i<n;i++){
            sum[i]=0;
            string tem="";
            for(int j=i;j<n;j++){
                tem+=s[j];
                v.emplace_back(tem,i);
            }
        }
        sort(v.begin(),v.end());
        ll ans=0;
        for(int f=0;f<v.size();f++){
            int fl=v[f].second;
            for(int k=fl+1;k<n;k++){
                ans=(ans+sum[k])%mod;
            }
            sum[fl]++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

H.Distance

大意:
给定多条线段,求一个中间点使到各个线段的距离都最短,并求和。

思路:
对顶堆维护中点

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
const int mod=1e9+7;
priority_queue<int,vector<int>,less<int>> q1;
priority_queue<int,vector<int>,greater<int>> q2;
int main()
{
    int n;
    ll ans=0;
    cin>>n;
    q1.push(-1e9-10);
    q2.push(1e9+10);
    for(int i=1;i<=n;i++){
        int l,r;scanf("%d%d",&l,&r);
        if(r<q1.top()){
            ans+=q1.top()-r;
            q2.push(q1.top());
            q1.pop();
            q1.push(l);
            q1.push(r);
        }else if(l>q2.top()){
            ans+=l-q2.top();
            q1.push(q2.top());
            q2.pop();
            q2.push(l);
            q2.push(r);
        }else {
            q1.push(l);q2.push(r);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

第四天

简单背包总结

1.完全背包

问题:
考虑有 n 种物品,第 i 种物品的每个重量为 wi,价值为 vi,有无限多个。 我们手头有一个大小为 m的背包,需要算出能装下的最大总价值。

思路:
设体积为j,f[j]表示体积为j时的最大价值。
易得大体积的最大价值可由小体积的最大价值推出。

代码:

for (int i = 1; i <= n; ++i) {//枚举物品 
    for (int j = w[i]; j <= m; ++j) {//枚举能装得下物品的体积 
        f[j] = max(f[j], f[j - w[i]] + v[i]); //正序,物品可以无限取 
    }
    //对每个物品考虑所有的体积情况 

另一种代码:

for (int i = 0; i <= m; ++i) {//枚举体积 
    for (int j = 1; j <= n; ++j) {//枚举物品 
        if (i - w[j] >= 0)
            f[i] = max(f[i], f[i - w[j]] + v[j]);
    } //对每个体积考虑最优物品 
}

注:
第二种代码与分组背包比较,
都是考虑每个体积最优的物品,
完全背包从前往后推,一种物品可能不只取1个。
分组背包从后往前推,倒序后每种物品只取1个,对每一组的物品取最优后满足组内互斥。

2.01背包

问题:
考虑有 n 种物品,第 i 种物品的每个重量为 wi,价值为 vi。 每种物品只有 1 件。 有一个大小为 m的背包,需要算出能装下的最大总价值。

思路:
对完全背包进行个数为1的限制。
由于从前往后推会出现多取,
那么从后往前(即倒序),前面全是未取的情况,故可以做到只取1个。

代码:

for (int i = 1; i <= n; ++i) { for (int j = m; j >= w[i]; --j) {// 倒序!!! f[j] = max(f[j], f[j - w[i]] + v[i]); } }

3.多重背包(朴素版)

问题:
考虑有 n 种物品,第 i 种物品的每个重量为 wi,价值为 vi。 第 i 种物品最多能取 ci 件。 有一个大小为 m的背包,需要算出能装下的最大总价值。

思路:
01背包的升级,个数不为1的限制。

代码:

for(int i=1;i<=n;i++){
	for(int l=1;l<=a[i];l++)//个数限制,多个01背包 
		    for(int j=tz;j>=l*t[i];j--) //倒序,前面确定的不要循环到(小优化) 
			{
				dp[j]=max(dp[j],dp[j-t[i]]+c[i]);
			}
}

4.二维费用背包

基本与一维处理方式相同,只是物品属性多了1个,需要二维数组。

代码:

 for(int i=1;i<=n;++i)
        for(int j=mv;j>=v[i];--j)
            for(int k=mw;k>=w[i];--k)
            dp[j][k]=max(dp[j][k],dp[j-v[i]][k-w[i]]+c[i]);

5.混合背包

最简单的是不同背包分开处理,
或者多重1背包当成限制个数不为1的01背包.

6.分组背包

组内物品互斥,
考虑先处理同1组的物品,确定好每个位置最优的同组物品,再考虑其他组。

代码:

for(int k=1;k<=num;k++)//枚举每一组 
        for(int j=m;j>=0;j--)//枚举体积 
            for(int i=1;i<=d[k];i++)//枚举同组内的每个物品 
            {
                int xx=dp[k][i];
                if(j>=w[xx])
                    f[j]=max(f[j],f[j-w[xx]]+vis[xx]);
            }

第五天

牛客训练营第六期

B.阿宁的倍数

大意:
一个长度为n的数组a,下标从1开始,有q次操作。
修改操作:数组末尾增加一个数x,数组长度加1。
询问操作:有多少个i(i>x)i(i > x)i(i>x),满足ai是ax的倍数?

代码:

# include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
vector<int> fac[N];
vector<int> pos[N];
int main(){
    for(int i = 1;i < N;++i){
        for(int j = i;j<N;j+=i){
            fac[j].push_back(i);
        }
    }
    int n,q;
    cin>>n>>q;
    vector<int> a(n+1);
    for(int i = 1;i <= n;++i){
        cin>>a[i];
        for(auto v:fac[a[i]]) pos[v].push_back(i);
    } 
    while(q--){
        int op,x;
        cin>>op>>x;
        if(op == 1){
            n++;
            a.push_back(x);
            for(auto v:fac[x]) pos[v].push_back(n);
        }
        else{
            int val = a[x];
            int p = (upper_bound(pos[val].begin(),pos[val].end(),x)-pos[val].begin());
            int ans = pos[val].size()-p;
            cout<<ans<<endl;
        }
    }
    return 0;
}

D.阿宁的毒瘤题

大意:
给定一个字符串,修改其中一个字符,使得udu的子序列最少。

思路:
对于每个位置的字符,算出与它关联的udu子序列个数,改掉最多的那个。

代码:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 3 + 2e5;
char s[N];
LL cnt[N];
LL preu[N], sufu[N];
int main() {
    cin >> s + 1;
    int n = strlen(s + 1);
    for (int i = 1; i <= n; ++i) {
        preu[i] = preu[i - 1];
        if (s[i] == 'u') {
            ++preu[i];
        }
    }
    for (int i = n; i >= 1; --i) {
        sufu[i] = sufu[i + 1];
        if (s[i] == 'u') {
            ++sufu[i];
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (s[i] == 'd') {
            cnt[i] = preu[i - 1] * sufu[i + 1];
        }
    }
    LL u = 0, d = 0;
    for (int i = 1; i <= n; ++i) {
        if (s[i] == 'u') {
            ++u;
            cnt[i] += d;
        } else if (s[i] == 'd') {
        
            d += u;
        }
    }
    u = 0;
    d = 0;
    
    for (int i = n; i >= 1; --i) {
        if (s[i] == 'u') {
            
            ++u;
            
            cnt[i] += d;
        } else if (s[i] == 'd') {
            
            d += u;
        }
    }


    LL temp = 0;
    for (int i = 1; i <= n; ++i) {
        temp = max(temp, cnt[i]);
    }
    
    for (int i = 1; i <= n; ++i) {
        if (temp == cnt[i]) {
            
            s[i] = 'a';
            
            break;
        }
    }

    cout << s + 1 << endl;
    return 0;
}

第六天

倍增的应用——ST表

任意整数可以表示成若干个2的次幂项的和,
因此可划分区间,高效地访问每一个整数。

ST表简单用于静态区间求最值,以及一些区间可以重复贡献的问题。

建议先预处理出2的幂,
for(int i=2;i<=n;i++)lg[i]=lg[i/2]+1;

代码:


#include <bits/stdc++.h>using namespace std;
#define ll long long#define endl '\n'const int N=1e5+10;
const int M=4*N;
const int mod=1e9+7;
ll f[N][35];
int lg[N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&f[i][0]);
    lg[1]=0;
    for(int i=2;i<=n;i++)lg[i]=lg[i/2]+1;
    for(int j=1;j<=lg[n];j++){
        for(int i=1;i<=n-(1<<j)+1;i++){
            f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
        }
    }
    for(int i=1;i<=m;i++){
        int l,r;
        scanf("%d%d",&l,&r);
        int len=lg[r-l+1];
        printf("%lld\n",max(f[l][len],f[r-(1<<len)+1][len]));
    }
    return 0;
}
posted @ 2023-02-05 19:00  WW爆米花  阅读(19)  评论(0编辑  收藏  举报