【 Educational Codeforces Round 57 (Rated for Div. 2)】A.B.C.D.F

前言

开场前打的牛客,状态很好,感觉晚上稳了,开场五分钟前滴了眼药水,准备好演算纸和模板,还剩30s的时候生病的女朋友突然说睡不着,于是纠结了20s之后用小号注册比赛,决定娱乐着打。结果很显然,一个紫名狗打绿名号没上蓝,可见专注和不专注还是不一样的,赛后补过了D和F,都是挺有意思的题。D算是经典套路,但是F应该算是思维细节题。
biu_biubiu r a t i n g + = 1 rating+=1 rating+=1 1581->1582


A. Find Divisible

题意

给你两个整数l,r,找出在l,r中的两个不相等的数x,y,满足y是x的倍数,保证有答案,多组答案输出一组即可

做法

很显然有答案的时候一定有r/2和r/2*2这组解,输出即可。

代码

#include<stdio.h>
int main()
{
    int t,l,r;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&l,&r);
        printf("%d %d\n",r/2,r/2*2);
    }
    return 0;
}


B. Substring Removal

题意

给你一个字符串,问移除一个字串之后使字符串为空或者只有一种字符的方案数。

做法

没想太多,对于每个左端点右端点答案满足二分性质
所以直接二分,nlogn过掉

代码

#include<stdio.h>
const int Mod = 998244353;
const int maxn = 2e5+10;
typedef long long ll;
char str[maxn];
int num[maxn][26];
int sum[26];
int len;
bool check(int pos,int mid)
{
    int cnt=0;
    for(int i=0;i<26;i++)
    {
        if(pos>0) sum[i]=num[len-1][i]-(num[mid][i]-num[pos-1][i]);
        else sum[i]=num[len-1][i]-num[mid][i];
        if(sum[i]) cnt++;
    }
    if(cnt<=1) return true;
    else return false;
}
int main()
{
    scanf("%d",&len);
    scanf("%s",str);
    for(int i=0;i<len;i++)
    {
        for(int j=0;j<26;j++)
        {
            if(str[i]-'a'==j) num[i][j]=num[i-1][j]+1;
            else num[i][j]=num[i-1][j];
        }
    }
    ll ans=0;
    for(int i=0;i<len;i++)
    {
        int l=i,r=len-1,mid;
        while(l<=r)
        {
            mid=l+r>>1;
            if(check(i,mid)) r=mid-1;
            else l=mid+1;
        }
        ans=(ans+(len-l))%Mod;
    }
    printf("%lld\n",ans);
    return 0;
}


C. Polygon for the Angle

题意

给一个角ang,如果一个正n边形,连接三个点之后角度为ang,则这个正n边形满足情况,求所有满足情况的多边形中最小的n,ang为整数,而且 1 &lt; = a n g &lt; 180 1&lt;=ang&lt;180 1<=ang<180

做法

一个正n边形构成的最小的角为180/n,能构成的所有角为
180/n,360/n…180-160/n,所以当n=180的时候,可以构造出除了179以外的所有角,而360可以构造出179,所以答案不超过180,我们只要遍历1-180看是否满足情况即可,满足情况的条件为,ang是180/n的倍数,而且ang<=180-360/n,我们发现两个条件都有除法,所以我们把除法化成乘法就可以了,第一个条件就是(angn)%180=0.第二个条件是angn<=180*n-360.

代码

#include<stdio.h>
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int ang;

        scanf("%d",&ang);
        int pos=-1;
        for(int i=3;i<=180;i++)
        {
            if((ang*i)%180==0&&ang*i<=180*i-360)
            {
                pos=i;
                break;
            }
        }
        if(pos!=-1) printf("%d\n",pos);
        else if(ang==179) printf("360\n");
        else
        {
            printf("180\n");
        }
    }
    return 0;
}


D. Easy Problem

题意

给你一个长度为n的字符串,每个点有一个删除的代价,问让字符串中不存在子序列hard的最小删除代价。
1 &lt; = n &lt; = 1 0 5 1&lt;=n&lt;=10^5 1<=n<=105
做法

定义
dp[i][0]为到i为止的序列中不存在h的最小代价
dp[i][1]为到i为止的序列中不存在ha的最小代价
dp[i][2]为到i为止的序列中不存在har的最小代价
dp[i][3]为到i为止的序列中不存在hard的最小代价
对于dp[i][0]状态,如果本位为h,那么一定要删除,
所以dp[i][0]=dp[i-1][0]+a[i]*(str[i]==‘h’);
而且dp[i-1][0]可以更新dp[i][1],
因为前一位不存在h,这一位肯定不存在ha
后面的更新同理。
代码

#include<stdio.h>
typedef long long ll;
#define min(x1,x2) x1<x2?x1:x2
const int maxn = 1e5+5;
const int Mod=1000000007;
int a[maxn];
char str[maxn];
ll dp[maxn][4];
int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",str+1);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<4;j++) dp[i][j]=dp[i-1][j];
        if(str[i]=='h')
        {
           dp[i][0]=dp[i-1][0]+a[i];
           dp[i][1]=min(dp[i][1],dp[i-1][0]);
        }
        if(str[i]=='a')
        {
            dp[i][1]=dp[i-1][1]+a[i];
            dp[i][2]=min(dp[i][2],dp[i-1][1]);
        }
        if(str[i]=='r')
        {
            dp[i][2]=dp[i-1][2]+a[i];
            dp[i][3]=min(dp[i][3],dp[i-1][2]);
        }
        if(str[i]=='d')
        {
            dp[i][3]=dp[i-1][3]+a[i];
        }
    }
    ll ans=1e18;
    for(int i=0;i<=3;i++) ans=min(ans,dp[n][i]);
    printf("%lld\n",ans);
    return 0;
}


F. Inversion Expectation

题意

给你一个数组,有些位置是-1,代表可以填数,要求填数之后数组为一个1-n的排列,求所有满足条件的填数方案中的逆序数的期望个数。

做法

记一共n个数,其中m个未知,因此可能的排列结果有m!种
我们分四种情况计数逆序对

1.已知和已知:简单的逆序对计数,对于每种排列贡献相同,所以要乘m!,做法可以用归并排序或者树状数组

2.未知和未知:考虑任意一对数会在一半的排列里成为逆序对,因此总共有 m ! ∗ ( m − 1 ) ∗ m 4 \frac{m!*(m-1)*m}{4} 4m!(m1)m

3.已知和未知:遍历每个已知数,考虑他前面的空位,以及未知数里有几个比他大的,不妨记为k个空位,s个比他大的数,那么考虑每个数可以放在某个空位,剩下数任意排列的方案数是(m-1)!,因此这个已知数和比他大的数构成逆序对贡献为(m-1)!ks

4.未知和已知:遍历每个已知数,考虑他后面的空位,以及未知数里有几个比他小的,不妨记为k个空位,s个比他小的数,那么考虑每个数可以放在某个空位,剩下数任意排列的方案数是(m-1)!,因此这个已知数和比他小的数构成逆序对贡献为(m-1)!ks

对以上逆序对数求和,就得到了所有情况下的逆序对总数;一共有m!种情况,期望就算出来了。过程理解起来简单,但是实现起来有些小细节,需要注意一下。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const int Mod=998244353;
int c[maxn];
int lowbit(int i)
{
    return i&(-i);
}
void add(int i)//i从1开始
{
    while(i<maxn)
    {
        c[i]++;
        i+=lowbit(i);
    }
    return ;
}
int sum(int i)//求和l,r.sum(r)-sum(l-1)
{
    int sum=0;
    while(i>0)
    {
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}
ll pow_(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%Mod;
		a=a*a%Mod;
		b>>=1;
	}
	return ans;
}
ll inv_(ll x)
{
	return pow_(x,Mod-2);
}
int a[maxn],pre[maxn],vis[maxn];
ll Fac[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	Fac[0]=1;
	for(int i=1;i<=n;i++) Fac[i]=Fac[i-1]*i%Mod;
	ll all=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]!=-1)
		{
			all++;
			vis[a[i]]++;
		}
	}
	int vv=0;
	for(int i=n;i>=1;i--)
	{
		pre[i]=vv;
		if(vis[i]) vv++;
	}
	ll ans=0,cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]==-1) cnt++;
		else
		{
			ans=(ans+Fac[n-all]*(sum(maxn-1)-sum(a[i]))%Mod+Mod)%Mod;
			add(a[i]);
			ans=(ans+1LL*(n-all-cnt)*(a[i]-all+pre[a[i]])%Mod*Fac[n-all-1]%Mod)%Mod;
			ans=(ans+1LL*cnt*(n-a[i]-pre[a[i]])%Mod*Fac[n-all-1]%Mod)%Mod;
		}
	}
	ans=(ans+Fac[cnt]*cnt%Mod*(cnt-1)%Mod*inv_((ll)4)%Mod)%Mod;
	ans=ans*inv_(Fac[cnt])%Mod;
	printf("%lld\n",ans);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值