Codeforces Round #496 (Div. 3) (A+B+C+D+E1+F)

A题 题目链接 (签到)
题意:给定一个序列,问可以分成多少段,满足从1开始,遇到1或末尾元素结束。
比如 1 2 | 1 2 3 | 1 分成3段 1 | 1 | 1 | 1 分成4段

思路:直接找每个1,当前1的下标减去上一个1的下标就是一段的长度。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=30000000;
const double eps=1e-5;
const int maxn=4e5+50;
int n;
vector<int> ans;
int a[1005];
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
    }
    int p1=1;
    for(int i=2;i<=n;i++)
    {
        if(a[i]==1)
        {
            ans.push_back(i-p1);
            p1=i;//更新最近的1的位置
        }
    }
    ans.push_back(n+1-p1);//末尾段
    cout<<ans.size()<<'\n';
    for(auto i:ans) cout<<i<<' ';
//    system("pause");
    return 0;
}

B题 题目链接 (字符串+模拟)

题意:给定两个字符串,我们可以每次选择1个字符串,从最左边删去一个字符,代价为1,问删至两个字符串相等的最少代价。

思路:由于这题是只能删最左边的,所以我们从最右边遍历找出最长的公共后缀。
然后len1+len2-公共后缀长*2 就是最少代价了

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=30000000;
const double eps=1e-5;
const int maxn=2e5+50;
char s1[maxn],s2[maxn];
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%s%s",s1+1,s2+1);
    int len1=strlen(s1+1),len2=strlen(s2+1);
    int same=0;//相同后缀长度
    int p1=len1,p2=len2;
    for(;p1>=1,p2>=1;p1--,p2--)
    {
        if(s1[p1]==s2[p2])
        {
            same++;
        }
        else break;
    }
    printf("%d\n",len1-same+len2-same);
  //  system("pause");
    return 0;
}

C 题题目链接 (map的运用)
题意:给定一个长度为n的序列,问其是否满足 ,任意一个元素都能找到另一个元素与之相加,和为2的n次幂。
1<=n<=1e5+2e4 1<=ai<=1e9

思路:这题刚开始觉得应该用map写, 对于每一个元素,在输入的时候预处理,令 map[k-a[i]]=i (2<=k<=2^30),映射为i是为了后面判断的时候,防止出现自己+自己的情况,具体实现看代码 。如果k-a[i]这个值出现大于1次,就改变映射为INF,说明不需要判断自己+自己的情况了。

但是一直tle。最后发现问题在于k的枚举方式那里

 int X=(int)pow(2,30);
    for(int i=1;i<=n;i++)
    {
        for(int k=2;k<=X;k*=2)
        {
            if(k<a[i]) continue;
            if(!mp[k-a[i]])  mp[k-a[i]]=i;//只有自己映射了
            else mp[k-a[i]]=INF;
        }
    }
    //这样枚举的话就tle
 for(int i=1;i<=n;i++)
    {
        int k=1;
        for(int j=1;j<=30;j++)
        {
            k*=2;
            if(k<a[i]) continue;
            if(!mp[k-a[i]])  mp[k-a[i]]=i;//只有自己映射了
            else mp[k-a[i]]=INF;
        }
    }
    //这也就ac了

AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
unordered_map<int,int> mp;
int n;
int a[maxn];
//(i & (i - 1)) == 0 说明i是2的n次幂
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*10+ch-'0';ch=getchar();}
    return x*f;
}
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    n=read();
    if(n==1)
    {
        puts("1");
        return 0;
    }
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        int k=1;
        for(int j=1;j<=30;j++)
        {
            k*=2;
            if(k<a[i]) continue;
            if(!mp[k-a[i]])  mp[k-a[i]]=i;//只有自己映射了
            else mp[k-a[i]]=INF;//多次取到 就说明不会出现自己+自己=2的n次幂这种情况
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        int x=mp[a[i]];
        if(!x  || x==i )//不存在映射 以及 只有和自己相加才能得到2的n次幂
        ans++;
    }
    printf("%d",ans);
//    system("pause");
    return 0;
}

D题 题目链接(贪心+dp)

题意:给定一串数,将其进行划分,输出最优划分下,最多的能被3整除的数字的个数。(不能含前导0)

思路:由贪心可知,如果某个位置的数是0 3 6 9 那么它一定要自己单独划分,因为其已经是3的倍数了。
基于贪心分析,发现前i个数可以由前i-1个数转移而来。

预处理:记录前缀和,前i个数的和

dp:
如果第i个数是3的倍数: dp[i]=dp[i-1]+1 用end_p记录最近的划分

否则向前枚举,如果枚举成功dp[i]= dp[i-1]+1,end_p=i ,枚举不成功dp[i]=dp[i-1]

具体转移过程见代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
char s[maxn];
bool isok[maxn];
int ans;
int sum[maxn];
int dp[maxn];//前i个数 对3取模为j 的最大个数
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%s",s+1);
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)
    {
        int id=s[i]-'0';
        sum[i]=sum[i-1]+id;//前缀和
        isok[i]=!(id%3);//记录每位是否是3的倍数
    }
    int end_p=0;//记录最近的划分位置
    dp[1]=isok[1];
    for(int i=2;i<=len;i++)
    {
        if(isok[i])
        {
            end_p=i;
            dp[i]=dp[i-1]+1;
        }
        else
        {
            dp[i]=dp[i-1];
            for(int j=i-2;j>=end_p;j--)//枚举,从i-1  一直到end_p
            {
                int x=sum[i]-sum[j];
                if(!(x%3)) 
                {
                    end_p=i;//更新最近划分
                    dp[i]++;
                    break;//找到了一个就可以直接退出
                }
            }
        }       
    }
    printf("%d",dp[len]);
    system("pause");
    return 0;
}

E1 题目链接 (数学+思维)

题意:给定一个长度为n的数列{p1,p2,…,pn}由{1,2,…,n}组成。
求满足中位数为m的闭区间[l,r]的个数。
(当区间内元素为偶,中位数取中间靠左的那个数)

思路:

我们先从一个区间中,中位数的特性开始分析:

  1. [l,r]包含m ,m为中位数
  2. 记大于m的数的个数为n_max,小于m的数的个数为n_min,
    3. n_max=n_min+0 / 1 也就是比m大的数 的个数 比 比m小的个数多0或1个

记m在[l,r]中的位置是index,
那么易得:记d1=n_max-n_min (区间 [index+1,r]中 )
d2= n_max-n_min (区间 [l,index-1]中 )

由等式3可知 d1+d2=0 或 1

那么,这题思路就比较清晰了,先找出m的下标index,然后遍历右边,如果a[i]>m ,令a[i]=1,小于则令其为-1。这样完成了一个类似区间优化的操作。

遍历右边的时候顺便记录sum[i],表示[index,i]的区间和,或者[i,index]的区间和,所以这里的区间和sum[i]就是区间[index+1,i]上的d1

再记录vis[i],表示index右边的区间中,区间和为i的区间个数 。由于i可能为负数,所以用maxn+i表示i

然后遍历index左边的元素,同样优化区间+记录区间和,这里的sum[i]就是区间[i,index-1]上的d2

由于 只要左边区间和+右边区间和=0/1 就满足条件,所以每次ans加上 vis[maxn+0-sum[i]+vis[maxn+1-sum[i]]即可

建议结合代码看,其实不难理解的~

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
int n,m;
int a[maxn];
int sum[maxn];
int vis[maxn*2];
//7 8 3 2 5 1 4 6 m=5,遍历更新 vis[0]=1 vis[-1]=1 vis[-2]=1 vis[-1]=2
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    LL ans=0;
    int cnt=0,index; 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        if(a[i]==m)
        {
            index=i;
            a[i]=0;
        }
    }
    vis[maxn+0]++;//自身为1个区间
    for(int i=index+1;i<=n;i++)
    {
        if(a[i]>m)
        {
            a[i]=1;
        }
        else if(a[i]<m)
        {
            a[i]=-1;
        }
        sum[i]=sum[i-1]+a[i];//[index,i]的区间和
        vis[maxn+sum[i]]++;//加maxn 防止出现负数 表示区间和为sum[i]的区间数目,即d1=sum[i]的区间数
    }
    ans+=vis[maxn+0-0]+vis[maxn+1-0];//此时d2=0  d1-d2=0/1 分别有0-0 1-0两种情况
    for(int i=index-1;i>=1;i--)
    {
        if(a[i]>m)
        {
            a[i]=1;
        }
        else if(a[i]<m)
        {
            a[i]=-1;
        }
        sum[i]=sum[i+1]+a[i];//这里sum[i]表示i到index的区间和
        ans+=vis[maxn+0-sum[i]]+vis[maxn+1-sum[i]];
    }
    cout<<ans;
    system("pause");
    return 0;
}

F题 题目链接 (最短路+dfs)

题意:给定一个无向图,求出以1为顶点的k个最短路树,用01串标记每条边是否选择,如果不够k个,输出所有的选择方案
/最短路树: 根到其余顶点的路权和最小。

思路:先dijkstra跑一遍,用vector < int> pre[i]记录第i个点进入最短路时,它的前驱边有哪些。

记录完毕之后,对于除出发点外任意一个顶点,其前驱边可以任选一条,所以总的方案数就是 pre[i].size()累乘起来。

然后从第二个点开始dfs,每个点的每条前驱边选和不选标记一下 。最后选完n个点的前驱边就输出dfs的方案。

#pragma comment(linker, "/STACK:102400000,102400000")///手动扩栈
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f;
const double eps=1e-5;
const int maxn=2e5+50;
//题意:给定一个无向图,求出以1为顶点的k个最短路树,用01串标记每条边是否选择,如果不够k个,输出所有的
//思路:最短路树就是 根到其余顶点的路权和最小。 用dijkstra跑一边,vecotr记录每个顶点的所有前驱边
struct 
{
    int to,next;
}edge[maxn<<1];
struct node
{
    int u,dis;
    friend bool operator < (const node & f1,const node &f2)
    {
        if(f1.dis!=f2.dis) return f1.dis>f2.dis;//大于号  表示小的优先
        else return f1.u>f2.u;
    }
};//用于dij
int head[maxn],cnt=1;
int num[maxn<<1];//前向星建图的边是第几条边
void add(int from,int to,int i)
{
    edge[cnt].to=to;
    edge[cnt].next=head[from];
    num[cnt]=i;
    head[from]=cnt++;
}
vector<int> pre[maxn];//记录点的所有入边
bool vis[maxn];//dij
int dist[maxn];//用于dij
int n,m,k;
char choose[maxn];//记录方案
void dijkstra(int x)
{
    fill(dist,maxn+dist,INF);
    dist[x]=0;
    priority_queue<node> q;
    q.push({x,dist[x]});
    while(!q.empty())
    {
        node temp=q.top();
        q.pop();
        if(vis[temp.u]) continue;
        vis[temp.u]=1;
        for(int i=head[temp.u];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(vis[to]) continue;
            if(dist[to]>dist[temp.u]+1)
            {
                pre[to].clear();
                pre[to].push_back(num[i]);
                dist[to]=dist[temp.u]+1;
                q.push({to,dist[to]});
            }
            else if(dist[to]==dist[temp.u]+1) 
            {
                pre[to].push_back(num[i]);//to的入边
            }
        }
    }
}
void dfs(int v)
{
    if(!k) return;
    if(v>n)//选了了n个点,已经选了n-1条边
    {
        k--;
        choose[m+1]='\0';//细节结尾,不然全给它输出喽
        printf("%s\n",choose+1);//边从1开始
        return;
    }
    for(int i=0;i<pre[v].size();i++)//枚举所有的入边
    {
        int e=pre[v][i];
        choose[e]='1';
        dfs(v+1);
        choose[e]='0';
    }
}
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d%d",&n,&m,&k);
    fill(choose,choose+maxn,'0');//初始化成不选
    for(int i=1;i<=m;i++)
    {
        int v,u;
        scanf("%d%d",&v,&u);
        add(v,u,i),add(u,v,i);
    }
    dijkstra(1);
    int tot=1;
    for(int i=2;i<=n;i++)
    {
        tot*=(int)pre[i].size();//总方案数
        if(tot>=k) break;
    }
    k=min(tot,k);
    printf("%d\n",k);
    dfs(2);//每个点的每个前驱都有被选择的机会,选出n-1条边即可
    system("pause");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值