第十一届“图灵杯“NEUQ-ACM程序设计竞赛题解

 A.古堡中的勇者
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,a,b,c,x,ans=0;
	vector<int>v;
	map<int,int>mp;
	cin>>a>>b>>c>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>x;
		mp[x]++;
	}
	for(auto it:mp)
		if(it.first>b&&it.first<c)  ans+=it.second;
	cout<<ans;
}
B.三星五费

至少要留下五个金币,然后一直刷新,直到用完金币为止。 

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,cnt=0;
    double ans=0;
    cin>>n;
    n-=5;
    while(n>1)
    {
        double x=0.03;
        for(int i=1;i<=cnt;i++)  x*=0.97;
        ans+=x;
        cnt++;
        n-=2;
    }
    printf("%.3lf",ans);
}
C.我就要不协调 

每个位置需要做的次数算一下,然后取最小值。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t,n,a[510];
	cin>>t;
	while(t--)
	{
		int ans=1e9;
		cin>>n;
		for(int i=1;i<=n;i++)  cin>>a[i];
		for(int i=2;i<=n;i++)
		{
			if(a[i]<a[i-1])  ans=0;
			else
			{
				int x=(a[i]-a[i-1])/2+1;
				ans=min(ans,x);
			}
		}
		cout<<ans<<endl;
	}
}
 D.古希腊掌管原神的神

2019ICPC南京站H题,题面比较不明所以。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int a,b,c;
    cin>>a>>b>>c;
    if(a>b+c)
    {
        if(a==1)  cout<<"YES\n0";
        else  cout<<"YES\n"<<2*(b+c)+1;
    }
    else  cout<<"NO";
}
E.字母匹配

对每种大小写字母贪心的使用次数,直到次数用完为止。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,k,x[26],y[26],ans=0;
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	string s;
	cin>>n>>k;
	cin>>s;
	for(int i=0;i<n;i++)
		if(s[i]>='A'&&s[i]<='Z')  x[s[i]-'A']++;
		else  y[s[i]-'a']++;
	for(int i=0;i<26;i++)
	{
		ans+=min(x[i],y[i]);
		int z=abs(x[i]-y[i])/2;
		if(k)
		{
			if(k>=z)  k-=z,ans+=z;
			else  ans+=k,k=0;
		} 
	}
	cout<<ans;
}
F.数学家的四不要

偶数不必多说,判断质数的时间复杂度是O(n\sqrt{n}),注意到卡特兰数增长很快,直接打表出哪些是卡特兰数即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,st[100010]={0};
	st[1]=st[5]=st[429]=1;
	cin>>n;
	for(int i=1;i<=n;i++)
        if(i%2==0)  st[i]=1;
	for(int i=2;i<=n;i++)
	{
		int flag=1;
		for(int j=2;j<=sqrt(i)+1;j++)
			if(i%j==0)
			{
				flag=0;
				break;
			}
		if(flag)  st[i]=1;
	}
	int cnt=1;
	while(cnt*(cnt+1)/2<=n)
	{
		st[cnt*(cnt+1)/2]=1;
		cnt++;
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		if(!st[i])  ans++;
	cout<<ans;
}
G.旗鼓相当的对手

动态规划,设dp[i][j]表示枚举到第i个物品相差为j的方案可行性,由于空间会爆炸,需要滚动数组。

#include<bits/stdc++.h>
using namespace std;
bool dp[2][200010];
int main()
{
	int n,a[510],ans=0;
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i];
    dp[0][0]=true;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=n*500;j++)
			dp[i&1][j]=dp[i&1^1][abs(j-a[i])]|dp[i&1^1][j+a[i]];
	for(int i=0;i<=500*n;i++)
		if(dp[n&1][i])
		{
			ans=i;
			break;
		}
	cout<<ans;
}
H.卷王

贪心,明显要把下次的最高分分配给小D,然后对于排在自己前面的人,优先把下次最少的分数分配给最靠前的人。如果这样还不能让其排名在自己后面,那不如趁机把下次最多的分数分配给他,以减少后面的人超过自己的机会。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,k,a[200010],b[200010];
	cin>>n>>k;
	for(int i=1;i<=n;i++)  cin>>a[i];
	for(int i=1;i<=n;i++)  cin>>b[i];
	int sum=a[k]+b[1],l=2,r=n,ans=1;
	for(int i=1;i<k;i++)
	{
		if(i==k)  continue;
		if(a[i]+b[r]>sum)  l++,ans++;
		else  r--;
	}
	cout<<ans;
}

关于本题的HACK数据:H题数据太弱,有问题_牛客网 (nowcoder.com)

I.子串比较

二分哈希。询问的原串是始终不变的,可以先对原串进行字符串哈希,然后对子串比较时,可以二分哈希值以找到两个子串第一次失配的位置,然后对这一位进行比较即可。时间复杂度最高为O(qlogn)。暴力解法可以在牛客上通过,比如下面这个:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,m,q;
    string s,t;
    cin>>n>>m>>q;
    cin>>s>>t;
    s=" "+s,t=" "+t;
    while(q--)
    {
        int l1,l2,r1,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        string x=s.substr(l1,r1-l1+1),y=t.substr(l2,r2-l2+1);
        if(x<y)  printf("<\n");
        else if(x==y)  printf("=\n");
        else  printf(">\n");
    }
}

可见牛客神机不是吹的,二分哈希方法如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=400010,P=131;
const ll mod=1e9+7;
ull p[N],x[N],y[N];
bool judge(int l1,int r1,int l2,int r2)
{
	if(x[r1]-x[l1-1]*p[r1-l1+1]==y[r2]-y[l2-1]*p[r2-l2+1])  return true;
	return false;
}
int main()
{
    int n,m,q;
    string s,t;
    cin>>n>>m>>q;
    cin>>s>>t;
    p[0]=1,x[0]=0,y[0]=0;
    s=" "+s,t=" "+t;
    for(int i=1;i<=n;i++)
    {
        p[i]=p[i-1]*P;
        x[i]=x[i-1]*P+s[i];
    }
    for(int i=1;i<=m;i++)  y[i]=y[i-1]*P+t[i];
    while(q--)
    {
        int l1,l2,r1,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(judge(l1,r1,l2,r2))  printf("=\n");
        else
        {
        	int l=0,r=m-1,ans=0;
        	while(l<=r)
        	{
        		int mid=l+r>>1;
        		if(judge(l1,l1+mid,l2,l2+mid))  l=mid+1,ans=mid+1;
        		else  r=mid-1;
			}
			if(s[l1+ans]>t[l2+ans])  printf(">\n");
			else  printf("<\n");
		}
    }
}
J.小C的学习计划

状压dp,用dp[i]表示二进制状态为i时距离学完还剩的期望步数。如果此时已经满足了重要程度大于等于m,明显dp[i]=0。否则进行推导,状态转移方程为dp[i]=\frac{c}{n}dp[i]+\sum \frac{1}{n}dp[i|j]+1 (c为i中二进制1的个数),移项之后即可求解dp[i]。(\frac{c}{n}dp[i]很明显是因为里面有c个1,这些都是学过的,因此学了等于白学;然后\frac{1}{n}dp[i|j]就是在原来i二进制位0的位置学了一次,相当于“定向学习”,因此是\frac{1}{n}而不是\frac{n-c}{n}。实际上i|j也只比i在二进制表示上多了一个1,因为每次只能学一科)

状态不能设置为0到i的期望步数,因为这样无法消除状态之间的影响,状态图会成环。

#include<bits/stdc++.h>
using namespace std;
int n,m,a[25];
double dp[1<<20];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)  cin>>a[i];
	for(int i=(1<<n)-1;i>=0;i--)
	{
		int sum=0,cnt=0;
		for(int j=0;j<n;j++)
			if(i>>j&1)  sum+=a[n-j],cnt++;
		if(sum>=m)  dp[i]=0;
		else
		{
			for(int j=0;j<n;j++)
				if(!(i>>j&1))  dp[i]+=1.0/(n-cnt)*dp[i|(1<<j)];
			dp[i]+=1.0*n/(n-cnt);
		}
	}
	printf("%.6lf",dp[0]);
}
K.同学聚会

gcd满足单调性,用线段树或者ST表维护区间gcd值,然后二分gcd不同的点,把gcd相同的区间答案合并计算,可以发现最多二分log(max(a_i))次,总复杂度是O(nlogloga_i)级别的。

对于nlog(a_i)的做法,我们可以固定左端点,然后用vector存储区间gcd的值以及该值对应子数组右端点的最大值,对于每段相同gcd的区间计算贡献。可以发现gcd相同的区间的长度是公差为1的等差数列,直接O(1)计算每个gcd的贡献即可。由于gcd最多变化log次,因此总时间复杂度为O(nlog(a_i))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353,inv=499122177;
ll fastpow(ll a,ll k)
{
	ll res=1;
	while(k)
	{
		if(k&1)  res=res*a%mod;
		k>>=1;
		a=a*a%mod;
	}
	return res;
}
int main()
{
	ll n,a[200010],ans=0;
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i];
	vector<pair<ll,int>>v;
	for(int i=n;i;i--)
	{
		v.push_back({a[i],i});
		v[0].first=__gcd(v[0].first,a[i]);
		int k=0;
		for(int j=1;j<v.size();j++)
		{
			v[j].first=__gcd(v[j].first,a[i]);
			if(v[j].first==v[k].first)  v[j].second=v[k].second;
			else  v[++k]=v[j];
		}
		v.resize(k+1);
		for(int j=1;j<v.size();j++)
		{
			ll x=v[j-1].first,y=v[j].second-i+2,z=v[j-1].second-i+1,num=v[j-1].second-v[j].second;
			ans=(ans+(ll)(y+z)*num%mod*inv%mod*x%mod)%mod;
		}
		ll x=v[v.size()-1].first,y=1,z=v[v.size()-1].second-i+1,num=v[v.size()-1].second-i+1;
		ans=(ans+(ll)(y+z)*num%mod*inv%mod*x%mod)%mod;
	}
	ll C=n*(n+1)%mod*inv%mod;
	ll sum=fastpow(C,mod-2);
	cout<<ans*sum%mod;
}

L.删边游戏

tarjan和树形dp?后面的回头再写

M.网格谜题

构造+大模拟+启发式合并

群里有后面几道题的题解

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值