数据结构,图论,dp简单题汇总

本文汇总了多个算法竞赛题目,包括CodeForces上的数论、图论、动态规划等各类问题的解题思路,涉及数组操作、树状数组、线段树、最短路径、集合操作等多种算法技巧。通过对这些题目的解析,展示了如何在不同场景下应用算法解决问题。
摘要由CSDN通过智能技术生成

水题解

  • 随便拉了几道题,感觉区域赛签到难度,cf1600-1700,写点题解。

MEX maximizing

题意:就是给你两个数q,x,接着q行数,每行代表像一个数组中插入这个数(初始数组为空)
然后你还可以在任意时刻对数组中的任意一个数的加减任意x倍数,问数组中最小没出现的数是多少,可以为0
思路:我的做法就是维护一个数mod x未出现的最小倍数,然后每次插入一个数,就mod x 在最小倍数插入这个数,然后最小倍数++,然后我们查询最小未出现的数是利用树状数组,如果查询前缀和(插入的数为1) c[x] == x 说明前x个数都插满了,我们倍增找出第一个不满足条件的数就是查询的答案。

另外朋友提供了权值线段树的思路,就是维护一个 1 − x + 1 1-x+1 1x+1的权值线段树,节点存入的是这个数出现的最小倍数,然后区间在维护最小,插入就是将数 m o d x + 1 mod x + 1 modx+1位置上

AC代码

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
const int maxn = 8e5+5;
const int N = 4e5+5;
ll c[maxn];
ll ct[maxn];
int q;
void change(ll v,ll pos){
    while(pos<=q){
        c[pos] += v;
        pos += lowbit(pos);
    }
}
int main(){
    int x;
    scanf("%d%d",&q,&x);
    int p = log2(q);
    // for(int i = 0;i<=q+10;i++)ct[i] = 1;
    
    int flag = 0;
    for(int i  = 0;i < q; i++){
        int y;
        scanf("%d",&y);
        ll mod = y % x;
        if(mod==0&&flag==0){
            flag = 1;
            ct[0]++;
        }
        else {
            if(ct[mod]*x+mod <= q)change(1,ct[mod]*x+mod);
            ct[mod]++;
        }

        if(flag==0){
            printf("0\n");
            continue;
        }
        else {
            ll sum = 0;
            int pos = 0;
            for(int j = p;j>=0;j--){
                // cout<<sum+c[(pos+(1<<j))]<<" "<<pos + (1<<j)<<c[(pos+(1<<j))]<<endl;
                if((pos+(1<<j))<=q&&sum+c[(pos+(1<<j))] == (pos + (1<<j))){
                    sum += c[pos+(1<<j)];
                    pos += (1<<j);
                }
            }
            printf("%d\n",pos+1);
        }
        
    }

}

Number of Ways

题意:将数组分成三份,然后问有多少种分法

思路:没想到数组是有序的,我想当然的以为数组是无序,误以为是个dp……谁知道就是思维,就是统计一下数组和的2/3倍之前有多少个数组和的1/3的位置,然后数组不能除以三输出答案0
AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn  = 5e5+5;
typedef long long ll;
ll sum[maxn];
// int a[maxn];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        ll x;
        scanf("%lld",&x);
        sum[i] = sum[i-1] + x;
    }
    if(sum[n]%3!=0){
        printf("0");return 0;
    }
    ll cnt1 = 0;
    ll ans = 0;
    for(int i = 1;i < n;i++){
        if(sum[n]/3*2==sum[i]){
            ans += cnt1;
        }
        
        if(sum[n]/3==sum[i]){
            cnt1++;
        }
        
    }
    cout<<ans;
}

Reading Books (easy version)

题意:有一堆书,两个人,读这一堆书,然后,每个书给定可以给A读,B读,或者一起读,然后每个数给定阅读时间,每个人都有一个做小阅读书目,然后问怎么选花时间最小(如果A和B看一本书时间就算一倍)
思路:我可能做的稍微有些麻烦,建立三个小根堆,然后存A可读,B可读,AB可读的书
然后如果A队头的加B堆头小于C堆头,就选用A和B的书(弹出队),否则选一起读的书(弹出队),然后在判一些细节

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<int, vector<int> ,greater<int> >q1,q2,q3;
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i = 0;i<n;i++){
        int t,a,b;
        scanf("%d%d%d",&t,&a,&b);
        if(a==1&&b==1){
            q3.push(t);
        }
        else if(a==1)q1.push(t);
        else if(b==1)q2.push(t);
    }
    if(min(q1.size(),q2.size())+q3.size()<k){
        printf("-1");return 0;
    }
    
    ll ans = 0;
    int cnt = 0;
    while(cnt<k){
        if(q3.empty()){
            ans += (q1.top()+q2.top());
            q1.pop();q2.pop();
        }
        else if(q1.empty()||q2.empty()){
            ans += (q3.top());
            q3.pop();
        }
        else if((q1.top()+q2.top())<q3.top()){
            ans += (q1.top()+q2.top());
            q1.pop();q2.pop();
        }
        else {
            ans += q3.top();
            q3.pop();
        }

        cnt++;
    }
    cout<<ans<<endl;
}

Linova and Kingdom

题意:是一个比较树上的问题,就是给一个树,然后一棵树上有两种节点,一个种工业节点,另一个种是旅游节点,然后我们我们在1节点召开会议,邀请所有工业节点的人来,然后我们我们要使来的人途径旅游节点的总和最大,问,要把那些节点设置为工业节点(设置k个工业节点)。

思路:我第一印象就是将叶子节点先设置为工业节点,但是多余的节点该怎么办,开始我以为是当然要设置最深的节点,但是我又考虑到每个节点子树大小的问题,比如一个节点子树很大,那么我们在这个节点设置工业节点就很不值,于是最后我想到了设置节点的代价,节点深度-子树大小,然后排个序,把最大的k个设为工业节点就好了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
struct edge{
    int v,next;
}e[maxn<<1];
int head[maxn],cnt = 0;
void add(int u,int v){
    e[cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt++;
}
struct Sz{
    int sz,id,deep;
}dp[maxn];
int deep[maxn],vis[maxn];
void dfs(int u,int fa){
    dp[u].sz = 1;
    dp[u].id = u;
    dp[u].deep = dp[fa].deep + 1;
    for(int i = head[u];~i;i=e[i].next){
        int v = e[i].v;
        if(v==fa)continue;
        dfs(v,u);
        dp[u].sz += dp[v].sz;
    }
}
bool cmp(Sz a,Sz b){
    // if(a.sz==b.sz)return a.deep>b.deep;
    // return a.sz<b.sz;
    return a.deep - a.sz > b.deep - b.sz; 
}
ll ans = 0;
void dfs2(int x,int fa){
    if(vis[x]==0)deep[x] = deep[fa] + 1;
    else {
        deep[x] = deep[fa];
        ans += deep[x];
    }
    for(int i = head[x];~i;i=e[i].next){
        int v = e[i].v;
        if(v==fa)continue;
        dfs2(v,x);
    }
}
int main(){
    int n,k;
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&k);
    for(int i = 0;i < n-1;i++){
        int v,u;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs(1,0);
    sort(dp+1,dp+n+1,cmp);
    for(int i = 1;i <= k;i++){
        vis[dp[i].id] = 1;
    }
    dfs2(1,0);
    cout<<ans<<endl;
}

Hard problem

题意:给你n个字符串,然后问你将这n个按字典序排序花费的最小代价是多少,不可以改变字符串的顺序,但是可以翻转字符串,翻转第k个字符串的代价为a[k],然后求最小代价

思路:dp,dp[i][j], d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表排到第i个字符串,并且第i个字符串为正序话费的最小代价, d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表排到第i个字符串,然后第i个字符串为反序最小的代价

#include <bits/stdc++.h>
// #define INF 1e16
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int c[maxn];
string str[maxn];
string rs[maxn];
ll dp[maxn][2];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        scanf("%d",&c[i]);
    }
    str[0] = "";
    // memset(dp,INF,sizeof dp);
    for(int i = 0;i <= n;i++){
        dp[i][0] = dp[i][1] = 1e16;
    }
    dp[0][1] = dp[0][0] = 0;
    for(int i = 1;i<=n;i++){
        cin>>str[i];
        rs[i] = str[i];
        reverse(rs[i].begin(),rs[i].end()); 
        // cout<<rs[i]<<"##"<<endl;
        if(str[i]>=str[i-1]){
            dp[i][0] = min(dp[i-1][0],dp[i][0]);
        }
        if(str[i]>=rs[i-1]){
            dp[i][0] = min(dp[i-1][1],dp[i][0]);
        }
        if(rs[i]>=str[i-1]){
            dp[i][1] = min(dp[i-1][0] + c[i],dp[i][1]);
        }
        if(rs[i]>=rs[i-1]){
            dp[i][1] = min(dp[i-1][1] + c[i],dp[i][1]);
        }
    }#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1 ;i <= m;i++){
        scanf("%d",&b[i]);
    }
    for(int i = 0;i<(1<<9);i++){
        int flag = 0;
        for(int j = 1;j <= n;j++){
            int f2 = 0;
            for(int k = 1;k <= m;k++){
                if(((a[j]&b[k])|i)==i){
                    f2 = 1;break;
                }
            }
            if(f2==0){
                flag = 1;break;#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1 ;i <= m;i++){
        scanf("%d",&b[i]);
    }
    for(int i = 0;i<(1<<9);i++){
        int flag = 0;
        for(int j = 1;j <= n;j++){
            int f2 = 0;
            for(int k = 1;k <= m;k++){
                if(((a[j]&b[k])|i)==i){
                    f2 = 1;break;
                }
            }
            if(f2==0){
                flag = 1;break;
            }
        }
        if(flag == 0){
            printf("%d",i);return 0;
        }
    }
}
            }
        }
        if(flag == 0){
            printf("%d",i);return 0;
        }
    }
}
    if(min(dp[n][0],dp[n][1])>=1e16){
        printf("-1\n");
    }
    else printf("%lld",min(dp[n][0],dp[n][1]));

}

Good Subarrays

题意:公式题,给一个序列要求求出满足 ∑ i = l r a i = r − l + 1 \sum\limits_{i=l}^{r} a_i = r - l + 1 i=lrai=rl+1的子序列个数

思路:转化一步原式等于 s u m i − s u m j = i − j sum_i -sum_{j}=i-j sumisumj=ij

然后在转化一步:
s u m i − i = s u m j − j sum_i-i=sum_{j}-j sumii=sumjj

我们直接算一个 s u m [ i ] − i sum[i]-i sum[i]i数组,然后找相等的,算个组合数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
ll sum[maxn];
int a[maxn];
map<int,ll>mp;
int main(){
    int t,n;
    scanf("%d",&t);
    while(t--){
        // getchar();
        scanf("%d",&n);
        for(int i = 1;i <= n;i++){
            scanf("%1d",&a[i]);
        }
        mp.clear();
        // cout<<"k"<<endl;
        for(int i = 1;i <= n;i++){
            sum[i] = sum[i-1] + a[i];
        }
        for(int i = 1;i <= n;i++){
            sum[i] -= i;
        }
        for(int i = 0;i <= n;i++){
            mp[sum[i]] += 1;
        }

        ll ans = 0;
        for(auto i :mp){
            ans += i.second*(i.second-1)/2;
        }
        printf("%lld\n",ans);
    }
}

Orac and LCM

思路:数论我不擅长,然后具体看别的题解说的就不写思路了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int a[maxn];
vector<ll>fat[maxn<<1];
ll Pow(ll a,ll b){
    ll ans = 1;
    while(b){
        if(b&1)ans = ans * a;
        b>>=1;
        a*=a;
    }
    return ans ;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1;i <= n;i++){
        // int tmp = a[i];
        for(int j = 2;j * j <= a[i];j++){
            // cout<<a[i]<<" "<<j<<" "<<tmp<<endl;
            int num = 0;
            while(a[i]%j==0){
                a[i] /= j;
                num++;
            }
            if(num!=0){
                // cout<<num<<endl;
                fat[j].push_back(num);
            }
        }
        if(a[i]!=1){
            fat[a[i]].push_back(1);
        }
    }
    long long ans = 1;
    for(int i = 2;i  < (maxn<<1);i++){
        if(fat[i].size() == n){
            sort(fat[i].begin(),fat[i].end());
            ans *= Pow(i,fat[i][1]);
        }
        else if(fat[i].size() == n-1){
            sort(fat[i].begin(),fat[i].end());
            ans *= Pow(i,fat[i][0]);
        }
    }
    printf("%lld",ans);
}

Anna, Svyatoslav and Maps

题意:给一个图,然后再给一个节点序列,求一个最短的序列,使得原序列是这个求得序列的最短路

思路:由于图并不大,所以先floyd预处理所有的两点间最短路,然后按照原数列走一遍,比如我们有一个最近的点在我们选取的点u,如果我们找到第i个点 d [ u ] [ i − 1 ] + d [ i − 1 ] [ i ] > d [ u ] [ i ] d[u][i-1] + d[i-1][i] > d[u][i] d[u][i1]+d[i1][i]>d[u][i]说明我们需要选第i-1的点


#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 105;
const int maxn = 1e6+6;
int a[N][N];
int dis[N][N];
int c[maxn];
vector<int> ans;
int main(){
    memset(dis,INF,sizeof dis);
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n; i++){
        for(int j = 1;j <= n;j++){
            int x;
            scanf("%1d",&x);
            if(x==1)dis[i][j] = 1;
            
        }
    }
    for(int i = 0;i<=n;i++){
        dis[i][i] = 0;
    }
    // for(int i = 1;i <= n; i++){
    //     for(int j = 1;j <= n;j++){
    //         cout<<a[i][j];
    //     }
    //     cout<<endl;
    // }
    for(int k = 1;k <= n;k++){
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= n;j++){
                if(dis[i][j]>dis[i][k]+ dis[k][j]){
                    dis[i][j]=dis[i][k] + dis[k][j];
                }
            }
        }
    }

    int m;
    scanf("%d",&m);
    for(int i = 1 ;i <= m;i++){
        scanf("%d",&c[i]);
    }
    // printf("%d",c[1]);
    int las = c[1];
    ans.push_back(las);
    for(int i = 2;i <= m;i++){
        // cout<<las<<"#"<<c[i]<<" "<<dis[las][c[i]] << " "<<dis[las][c[i-1]] + dis[c[i-1]][c[i]]<<endl;
        if(dis[las][c[i]] < dis[las][c[i-1]] + dis[c[i-1]][c[i]]){
            // printf(" %d",c[i-1]);
            ans.push_back(c[i-1]);
            las = c[i-1];
        }
    } 
    ans.push_back(c[m]);
    cout<<ans.size()<<endl;
    for(auto i : ans){
        printf("%d ",i);
    }
}

Boboniu Chats with Du

题意:有n天一个男生给女生发QQ如果然后女生对男生每天有一个厌烦值,男生每天可以选择法或不发,但是如果都一天男生的厌烦度超过了女生的阈值,女生就要禁言了男生k天,k也给定,问男生如何选择才生使女生积累的厌烦值最大

思路:枚举一下男生超过女生阈值的天数,假如有2个,那么我们安排俩小弟在这两天就可以了,模拟一下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int bg[maxn],sm[maxn];
ll sum[maxn];
ll sum2[maxn];
int cmp(ll x,ll y){
    return x>y;
}
int main(){
    int n,d,m;
    cin>>n>>d>>m;
    int cnt1=1,cnt2=1;

    for(int i = 1;i <= n; i++){
        int x;
        scanf("%d",&x);
        if(x<=m)sm[cnt2++] = x;
        else bg[cnt1++] = x;
    }

    sort(bg+1,bg+cnt1,cmp);
    for(int i = 1;i < cnt1;i++){
        sum[i] = sum[i-1] + bg[i];
    }
    sort(sm+1,sm+cnt2,cmp);
    for(int i = 1;i < cnt2;i++){
        sum2[i] = sum2[i-1] + sm[i];
        // cout<<sum2[i]<<"#"<<endl;
    }
    int mx = (n-1)/(d+1)+1;
    ll ans = 0;
    for(int i = 0;i < cnt1;i++){
        ll p=1ll*(i-1)*d+i;
		if(p>n)continue;
        if(i==0)ans = max(ans,sum2[cnt2-1]);
        else if((1LL*(i-1)*d -(cnt1-1-i))> 0){
            ans = max(ans,sum[i] + sum2[cnt2 -1- (1LL*(i-1)*d-(cnt1-1-i))]);
        }
        else {
            ans = max(ans,sum[i] + sum2[cnt2-1]);
        }
    }
    cout<<ans<<endl;
}

Cow and Snacks

题意:一共有1-n个点心,每个人喜欢吃两个点心,如果一个人喜欢吃的两个点心都被别人吃了,那么他就会sad,然后问sad人最小是多少

思路:就是一个人喜欢吃的两个点心相连,我们发现,一旦出现一个环sad人数++,并查集记录一下就好了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int fa[maxn];
int get(int x){
    if(fa[x]==x)return x;
    return fa[x] = get(fa[x]);
}
int merge(int x,int y){
    int p = get(x);
    int q = get(y);
    if(p!=q){
        fa[p] = q;
        return 1;
    }
    return 0;
}
int main(){
    int n,k;
    int ans = 0;
    scanf("%d%d",&n,&k);
    for(int i = 0 ;i <= n;i++)fa[i] = i;
    for(int i = 1;i <= k;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        if(!merge(x,y)){
            ans++;
        }
    }
    printf("%d",ans);
}

Boboniu and Bit Operations

  • CodeForces - 1395C
    题意:给你两个数组a,b然后a中每个元素在b中选取一个元素,然后两个元素向&,然后得到的所有结果向|,得到最后的答案最大是多少

思路:枚举答案,因为最终结果不超过1e9,然后就是,我们判断结果是否可能是这个数,就是需要

#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1 ;i <= m;i++){
        scanf("%d",&b[i]);
    }
    for(int i = 0;i<(1<<9);i++){
        int flag = 0;
        for(int j = 1;j <= n;j++){
            int f2 = 0;
            for(int k = 1;k <= m;k++){
                if(((a[j]&b[k])|i)==i){
                    f2 = 1;break;
                }
            }
            if(f2==0){
                flag = 1;break;
            }
        }
        if(flag == 0){
            printf("%d",i);return 0;
        }
    }
}

Eugene and an array

题意:就是给一个串,问这个串有多少字串的和不是0

思路:先处理一个前缀和,然后在每个位置判断这个位置之前第一个前缀和等于自身的点,然后答案加中间的数,类似尺取的方法

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
ll sum[maxn];
map<ll,int>mp;
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n ;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1;i <= n;i++){
        sum[i] = sum[i-1] + a[i];
    }
    ll ans = 0;
    // mp.clear();
    mp[0] = 1;

    int l = 0;
    for(int i = 1;i <= n; i++){
        ans += i - max(l,mp[sum[i]]);
        l = max(l,mp[sum[i]]);
        mp[sum[i]] = i+1;
    }
    cout<<ans<<endl;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值