[NOIP模拟测试38]题解

来自达哥的问候……

A.金

显然本题的考察点在于高精而不是裴蜀定理

根据裴蜀定理易得答案为Yes当且仅当$gcd(n,m)=1$,那么考虑怎么在高精度下判互质。

如果$n,m$都能被2整除,那么显然不互质。

如果其中一个可以而另一个不可以(以n能被2整除为例),$gcd(n,m)$就可以转化为$gcd(\frac{n}{2},m)$

如果两个数都不是2的倍数,根据更相减损术得到$gcd(n,m)=gcd(n,|n-m|)$

重复这个过程即可。因为奇数减奇数一定是偶数,所以第三种操作不会连续进行两次,复杂度是log的。

 

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=505;
int T,p[N],q[N];
ll n,m;
ll gcd(ll x,ll y)
{
    if(!y)return x;
    return gcd(y,x%y);
}
void print(int a[])
{
    for(int i=a[0];i;i--)
        cout<<a[i];
    cout<<endl;
}
void ini(int a[])
{
    char s[N];
    scanf("%s",s);
    a[0]=strlen(s);
    for(int i=1;i<=a[0];i++)
        a[i]=s[a[0]-i]-'0';
}
ll turnn(int a[])
{
    ll res=0;
    for(int i=a[0];i;i--)
        res=res*10+a[i];
    return res;
}
int cmp(int a[],int b[])
{
    if(a[0]>b[0])return 1;
    if(a[0]<b[0])return 0;
    for(int i=a[0];i;i--)
    {
        if(a[i]>b[i])return 1;
        if(a[i]<b[i])return 0;
    }
    return -1;
}
void jian(int a[],int b[])
{
    int fl=cmp(a,b);
    if(fl==-1)
    {
        a[0]=0;
        return ;
    }
    if(fl==1)
    {
        for(int i=1;i<=a[0];i++)
        {
            if(a[i]<b[i])
            {
                a[i+1]--;
                a[i]+=10;
            }
            a[i]-=b[i];
        }
        while(a[0]>0&&a[a[0]]==0)a[0]--;
        return ;
    }
}

bool is2(int a[])
{
    if(a[1]==2||a[1]==0||a[1]==4||a[1]==6||a[1]==8)return 1;
    return 0;
}
bool is3(int a[])
{
    ll res=0;
    for(int i=a[0];i;i--)
        res+=a[i];
    if(res%3==0)return 1;
    return 0;
}
void divi(int a[],int b)
{
    int x=0;
    for(int i=a[0];i;i--)
    {
        int old=(x*10+a[i])/b;
        x=(x*10+a[i])%b;
        a[i]=old;//cout<<a[i]<<endl;
    }
    while(a[0]>0&&a[a[0]]==0)a[0]--;
    return ;
}
bool leg(int a[],int b[])
{
    while(a[0]&&b[0])
    {
        //print(a);print(b);
        //cout<<a[0]<<' '<<b[0]<<endl;
        if(is2(a)&&is2(b)){return 0;}
        if(is2(a))divi(a,2);
        else if(is2(b))divi(b,2);
        else
        {
            if(cmp(a,b))jian(a,b);
            else jian(b,a);
        }

    }
    if(a[0]==a[1]&&a[0]==1)return 1;
    if(b[0]==b[1]&&b[0]==1)return 1;
    return 0;
}
void qj1()
{

    if(m==1)
    {
        puts("Yes");return ;
    }
    if(m==0||n==0)
    {
        puts("No");return ;
    }
    if(gcd(n,m)==1)
    {
        puts("Yes");
    }
    else puts("No");
    return ;

}
void work()
{
    ini(p);ini(q);
    if(p[0]<=18&&q[0]<=18)
    {
        n=turnn(p);m=turnn(q);
        qj1();
        return ;
    }
    if(p[0]==1&&p[1]==0)
    {
        puts("No");
        return ;
    }
    if(q[0]==1&&q[1]==0)
    {
        puts("No");
        return ;
    }
    if(q[0]==1&&q[1]==1)
    {
        puts("Yes");
        return ;
    }
    if(q[0]==1&&(q[1]==2||q[1]==3))
    {
        m=turnn(q);
        if(m==2)
        {
            if(is2(p))puts("No");
            else puts("Yes");
        }
        if(m==3)
        {
            if(is3(p))puts("No");
            else puts("Yes");
        }
        return ;
    }
    if(leg(p,q))puts("Yes");
    else puts("No");
    return ;
}
int main()
{
    //while(1)ini(p),divi(p,2),print(p),cout<<p[0]<<endl;
    scanf("%d",&T);
    while(T--)work();
    return 0;
}

 

B.斯诺

首先考虑序列中只有0和1的情况,这时区间合法当且仅当区间中0的个数等于区间中1的个数。开桶维护,下标为区间中0的个数和1的个数的差值。直接利用前缀和扫一遍即可,复杂度$O(n)$,结合暴力可以得到60分。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=5e6+5;
int n,a[N],sum[4][N];
ll ans,cnt,bu1[N],bu2[N];
char s[N];
void qj1()
{
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
		{
			int len=j-i+1,ok=1;
			for(int k=0;k<=2;k++)
				if((sum[k][j]-sum[k][i-1])*2>len)
				{
					ok=0;break;
				}
			if(ok)ans++;
		}
	cout<<ans<<endl;
}
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)
	{
		a[i]=s[i]-'0';
		for(int k=0;k<=2;k++)
			sum[k][i]=sum[k][i-1]+(a[i]==k);
		if(sum[0][i]==sum[1][i])cnt++;
	}

	if(n<=3000)
	{
		qj1();
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		ll m;
		if(sum[0][i]>=sum[1][i])
		{
			ans+=bu1[sum[0][i]-sum[1][i]];
			bu1[sum[0][i]-sum[1][i]]++;
			if(sum[0][i]==sum[1][i])ans++;
		}
		else
		{
			ans+=bu2[sum[1][i]-sum[0][i]];
			bu2[sum[1][i]-sum[0][i]]++;
		}
	}
	cout<<ans<<endl;
	return 0;
}

 

那么对于所有情况,显然区间中最多有1个数字数量超过一半,那么合法区间数就是总区间数($\frac{n\times (n+1)}{2}$)减去0超过一半的区间数、1超过一半的区间数和2超过一半的区间数。

对于每种数字维护前缀和,把多一个这种数字看作-1,多一个其它数字看作+1,那么问题转化为求前缀和数组的逆序对个数。可以上树状数组,注意序列要扫到0(因为前缀和的柿子是$sum[r]-sum[l-1]$)。另外,为了防止下标负数要集体加上一个值。复杂度$O(n\ log\ n)$,可以得到90分。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=5e6+5;
int n,a[N],sum[3][N],c[3][N],minn[3]={0x3f3f3f3f,0x3f3f3f3f,0x3f3f3f3f};
ll ans;
char s[N];
int lb(int x){return x&-x;}
void add(int k,int x,int val)
{
    for( ;x<=n;x+=lb(x))
        c[k][x]+=val;
}
ll query(int k,int x)
{
    ll res=0;
    for( ;x;x-=lb(x))
        res+=c[k][x];
    return res;
}
void show(int k)
{
    puts(" ");
    for(int i=1;i<=n;i++)
        cout<<sum[k][i]<<' ';
    puts(" ");
}

int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
    {
        a[i]=s[i]-'0';
        for(int k=0;k<=2;k++)
            sum[k][i]=sum[k][i-1]+(a[i]==k?-1:1),minn[k]=min(minn[k],sum[k][i]);
    }
    for(int k=0;k<=2;k++)
    {
        for(int i=0;i<=n;i++)
            sum[k][i]+=minn[k]<0?(-minn[k]+1):1;
        for(int i=n;i>=0;i--)
            ans+=query(k,sum[k][i]-1),add(k,sum[k][i],1);
        //cout<<ans<<endl;
        //show(k);
    }
    cout<<1LL*n*(n+1)/2-ans<<endl;
    return 0;
}

 

注意到这个前缀和数组有特殊性:相邻两项的差值最大为1。因此,相邻两项对答案的贡献也最多相差一种值的个数,开桶维护即可。复杂度$O(n)$。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=5e6+5;
int n,a[N],sum[3][N],bu[3][N],minn[3]={0x3f3f3f3f,0x3f3f3f3f,0x3f3f3f3f};
ll ans;
char s[N];
int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
    {
        a[i]=s[i]-'0';
        for(int k=0;k<=2;k++)
            sum[k][i]=sum[k][i-1]+(a[i]==k?-1:1),minn[k]=min(minn[k],sum[k][i]);
    }
    for(int k=0;k<=2;k++)
    {
        for(int i=0;i<=n;i++)
            sum[k][i]+=minn[k]<0?(-minn[k]+1):1;
        ll last=0;
        for(int i=n;i>=0;i--)
        {
            if(i!=n)
            {
                if(sum[k][i]==sum[k][i+1]+1)ans+=last+bu[k][sum[k][i+1]],last+=bu[k][sum[k][i+1]];
                else if(last&&sum[k][i]==sum[k][i+1]-1)ans+=last-bu[k][sum[k][i]],last-=bu[k][sum[k][i]];
                else ans+=last;
            }
            bu[k][sum[k][i]]++;
        }
    }
    cout<<1LL*n*(n+1)/2-ans<<endl;
    return 0;
}

 

C.赤

竟然是wqs二分套wqs二分,$n^3$硬生生砍成$n\ log^2 n$,太残暴了QAQ。

首先暴力dp应该很好想,$dp[i][j][k]$表示遇到了i只猫,丢了j包干脆面和k包豆干。直接三层循环转移即可,边界稍微修剪一下可以得到50分。(然而考场上写着写着莫名其妙丢了一个方程)

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,a,b;
double dp[3][2005][2005],p[100005],q[100005];
void work()
{
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
        scanf("%lf",&p[i]);
    for(int i=1;i<=n;i++)
        scanf("%lf",&q[i]);
    double ans=0;
    //dp[1][1][0]=p[1];dp[1][0][1]=q[1];dp[1][1][1]=p[1]+q[1]-p[1]*q[1];
    int now=0,pre=1;
    for(int i=1;i<=n;i++)
    {
        now^=1,pre^=1;
        for(int j=0;j<=min(i,a);j++)
            for(int k=0;k<=min(i,b);k++)
                dp[now][j][k]=0;
        for(int j=0;j<=min(i,a);j++)
        {
            for(int k=0;k<=min(i,b);k++)
            {
                dp[now][j][k]=max(dp[pre][j][k],dp[now][j][k]);//就是它!
                if(j>0)dp[now][j][k]=max(dp[pre][j-1][k]+p[i],dp[now][j][k]);
                if(k>0)dp[now][j][k]=max(dp[pre][j][k-1]+q[i],dp[now][j][k]);
                if(j>0&&k>0)dp[now][j][k]=max(dp[pre][j-1][k-1]+p[i]+q[i]-p[i]*q[i],dp[now][j][k]);
                //cout<<i<<' '<<j<<' '<<k<<' '<<dp[now][j][k]<<endl;
            }
        }
    }
    for(int i=0;i<=min(n,a);i++)
        for(int j=0;j<=min(n,b);j++)
            ans=max(ans,dp[now][i][j]);
    printf("%.3lf\n",ans);
}
int main()
{
    while(scanf("%d%d%d",&n,&a,&b)!=EOF)
        work();
    return 0;
}

 

观察这个转移发现是很套路的wqs?不过辣鸡博主根本没写过wqs当然看不出来了。

具体分析就直接放达哥官方题解好了(逃

我们首先不考虑 b 的限制,假设豆干可以任意使用,定义 $f[i][j]$表示前 i 只猫使用 j 包干脆面
和若干包豆干得到的最大收益。

直接这么 DP 会 n 只猫都用豆干,可能会超出 b 的限制。


为了减少豆干的使用,我们可以假定使用一个豆干需要额外付出 cost 的代价(也就是在转移
的时候如果一只猫用了豆干,它对期望值的贡献要减去 cost)。

在 DP 的时候,求解出 $f[i][j]$的最大值,并记录最优方案中豆干使用的数目 x,那么此时真实的期望是 $f[i][j]$的最优值加上
$x*cost$,这个结果必然也是用了 x 个豆干时能够得到的最优结果。


但是,如果我们随便假定一个 cost 去跑 DP,得到的方案并不一定把所有豆干都用完,我们需
要一个能把所有豆干都用完的 cost 的值。


显然豆干使用量随着 cost 的变化是单调变化的,我们可以二分 cost 的数值.

 

这样dp数组就减少了一维,反映在时间复杂度上就是少了个n多了个log。同样地,我们还可以再砍掉一维,然后二分套二分解决。复杂度$O(n\ log^2 \ n)$。

#define hzoj

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const double eps=1e-9;
int n,a,b,na[100005],nb[100005];
double dp[100005],p[100005],q[100005];
int check(int op,double c1,double c2)
{
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1];
        na[i]=na[i-1];nb[i]=nb[i-1];
        if(dp[i-1]+p[i]-c1>dp[i])dp[i]=dp[i-1]+p[i]-c1,na[i]=na[i-1]+1,nb[i]=nb[i-1];
        if(dp[i-1]+q[i]-c2>dp[i])dp[i]=dp[i-1]+q[i]-c2,nb[i]=nb[i-1]+1,na[i]=na[i-1];
        if(dp[i-1]+p[i]+q[i]-p[i]*q[i]-c1-c2>dp[i])dp[i]=dp[i-1]+p[i]+q[i]-p[i]*q[i]-c1-c2,na[i]=na[i-1]+1,nb[i]=nb[i-1]+1;
    }
    if(op==1)return nb[n];
    else return na[n];
}

void work()
{
#ifdef luogu
    scanf("%d%d%d",&n,&a,&b);
#endif
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
        scanf("%lf",&p[i]);
    for(int i=1;i<=n;i++)
        scanf("%lf",&q[i]);
    double l1=0.0,r1=1.0,l2,r2,mid1,mid2;
    while(r1-l1>eps)
    {
        mid1=(l1+r1)/2;
        l2=0.0,r2=1.0;
        while(r2-l2>eps)
        {
            mid2=(l2+r2)/2;
            if(check(1,mid1,mid2)>b)l2=mid2;
            else r2=mid2;
        }
        if(check(2,mid1,r2)>a)l1=mid1;
        else r1=mid1;
    }
    //check(0,r1,r2);
    printf("%.3lf\n",dp[n]+1.0*b*r2+1.0*a*r1);
}
int main()
{
#ifdef hzoj
    while(scanf("%d%d%d",&n,&a,&b)!=EOF)
#endif
        work();
    return 0;
}

 

posted @ 2019-09-07 11:01  Rorschach_XR  阅读(240)  评论(2编辑  收藏  举报
//雪花飘落效果