关于2019NKOJ4月月赛

本文详细解析了NKOJ2019年四月月赛的五道题目,包括切火腿肠、翻硬币、三分数组、观光车和看电影等问题,涵盖算法如前缀和、贪心算法、状态压缩DP和扩展欧几里得算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NKOJ2019四月月赛

这场比赛打得是心态爆炸。该拿的分没拿完。哎,自己是真的菜。

A.切火腿肠(NKOJ4737NKOJ4737NKOJ4737)

何老板有N根大小相同且质地均匀的火腿肠,要分给M名信竞队员。要求每名队员分得的香肠重量相同
何老板想知道,最少切多少刀就能满足上述要求?
一行,两个整数N和M,(1&lt;=N,M&lt;=10001&lt;=N,M&lt;=10001<=N,M<=1000)

解:

假设每根火腿肠长度为LLL,则总长度为S=N×LS=N \times LS=N×L
所以每个人能够分到的长度为:averge=SM=N×LM\\ averge=\frac{S}{M}=\frac{N \times L}{M}averge=MS=MN×L
一根长度为S的火腿需要切:
Saverge=N×LN×LM=M \frac{S}{averge}=\frac{N \times L}{\frac{N \times L}{M}}=M avergeS=MN×LN×L=M
此时考虑某一个切点恰好是位于两根火腿之间的
设最少第TTT刀在两根火腿之间,则:
k×L=averge×T=N×L×TM⟹T=k×MN k\times L=averge\times T=\frac{N\times L\times T}{M}\Longrightarrow T=\frac{k\times M}{N} k×L=averge×T=MN×L×TT=Nk×M
∵T,k∈N∗且要T最小\because T,k\in N^*且要T最小T,kNT
∴T=lcm(M,N)N\therefore T=\frac{lcm(M,N)}{N}T=Nlcm(M,N)
∴ans=M−MT=M−Mlcm(M,N)N=M−gcd(N,M)\therefore ans=M-\frac{M}{T}=M-\frac{M}{\frac{lcm(M,N)}{N}}=M-gcd(N,M)ans=MTM=MNlcm(M,N)M=Mgcd(N,M)

注:这题规模很小,可以直接暴力

#include<bits/stdc++.h>
using namespace std;

int gcd(int a,int b){return b==0?a:gcd(b,a%b);}

int main()
{
	int n,m;
	cin>>n>>m;
	cout<<m-gcd(n,m);
	return 0;
}

B.翻硬币(NKOJ5590NKOJ5590NKOJ5590)

何老板将nnn个硬币排成一排。从左往右编号111nnn。有的正面(国徽)朝上,有的背面(面额)朝上。
 为方便表示,何老板用字母BBB代表正面朝上,WWW代表背面朝上。于是就得到一个由大写字母BBBWWW构成的长度为nnn的字符串SSS

对任意相邻的两个硬币iiii+1i+1i+1号硬币(1&lt;=i&lt;n)(1&lt;=i&lt;n)(1<=i<n)。若满足iii是正面朝上,i+1i+1i+1是背面朝上,你就可以对它们进行翻转操作,翻转后iii的背面朝上,i+1i+1i+1的正面朝上。

何老板想知道,最多能进行多少次上述翻转操作?

其实我们可以贪心一下,如果要进行操作的次数最多,则最好把所有的BBB都移到右边,那么对于每一个WWW,则它会被在它左边的每一个BBB都交换一次,所以答案就很显然了

#include<bits/stdc++.h>
using namespace std;

int main()
{
	char c;
	long long ans=0,x=0;
	while((c=getchar())!=EOF)
	{
		if(c=='W')ans+=x;
		else x++;
	}
	cout<<ans;
	return 0;
}

C.三分数组(NKOJ3049)(NKOJ3049)(NKOJ3049)

给出一个有nnn个整数的数组a1,a2,...,ana_1,a_2,...,a_na1,a2,...,an, 有多少种方法把数组分成333个连续的子序列,使得各子序列的元素之和相等。也就是说,有多少个下标对i,j(2≤i≤j≤n−1),i,j (2≤i≤j≤n-1),i,j(2ijn1),满足:sum(a1..ai−1)=sum(ai..aj)=sum(aj+1..an)sum(a_1..a_{i-1}) = sum(a_i..a_j) = sum(a_{j+1}..a_n)sum(a1..ai1)=sum(ai..aj)=sum(aj+1..an)
111 行:111 个整数n(1&lt;=n&lt;=5∗105)n(1 &lt;= n &lt;= 5*10^5)n(1<=n<=5105)
接下来n 行,每行1 个整数,表示ai(∣ai∣&lt;=109)a_i( |a_i| &lt;= 10^9)ai(ai<=109)

首先先前缀和处理,然后再判断3∣sum[n]3|_{sum[n]}3sum[n],若不整除,则无解。如果整除:
定义averge=sum[n]3averge=\frac{sum[n]}{3}averge=3sum[n]
考虑:
对于每一个sum[j]=averge×2sum[j]=averge\times 2sum[j]=averge×2的位置,它能组成的下标对的个数就是在它前面的sum[i]=avergesum[i]=avergesum[i]=averge的个数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
ll a[maxn],sum[maxn],ans;
ll n;
inline void rin(ll &t)
{
	char c=getchar();
	t=0;
	int k=1;
	while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}
	while(isdigit(c)){t=t*10+c-'0';c=getchar();}
	t*=k;
} 

int main()
{
	rin(n);
	for(int i=1;i<=n;i++)rin(a[i]),sum[i]=sum[i-1]+a[i];
	if(sum[n]%3){puts("-1");return 0;}
	ll averge=sum[n]/3;
	int cnt=0;
	if(sum[1]==averge)cnt=1;
	for(int i=2;i<n;i++)
	{
		if(sum[i]==averge*2)ans+=cnt;//这里两个判断语句不能调换,想一想,为什么? 
		if(sum[i]==averge)cnt++;
	}
	cout<<ans;
	return 0; 
} 

D.观光车(NKOJ3677)(NKOJ3677)(NKOJ3677)

何老板带领nnn名游客来到一景区大门口,需要乘坐观光车游览景区。

景区提供两种观光车,一种是每辆车可以坐aaa名游客,包一辆车费用是p1p_1p1块钱;另一种每辆车可以坐b名游客,包一辆车费用是p2p_2p2块钱。

何老板想让这nnn名游客都坐上观光车,且每辆车都坐满。问何老板至少要花费多少钱?

设坐xxx辆价格为p1p_1p1的车,yyy辆价格为p2p_2p2的车
首先先解出不定方程
ax+by=n ax+by=n ax+by=n
先判断是否有解,若有解:
求:
ans=p1x+p2y(x≥0,y≥0)的最大值 ans=p_1 x+p_2 y (x\ge 0,y\ge 0)的最大值 ans=p1x+p2y(x0,y0)
扩展欧几里得算法可以得出:
{x=x0+k×bdy=y0−k×ad \begin{cases} x=x_0+k\times \frac{b}{d}\\ y=y_0-k\times \frac{a}{d} \end{cases} {x=x0+k×dby=y0k×da
∵x≥0并且y≥0∴k∈[⌈−x0×db⌉,⌊y0×da⌋]且k∈N \because x\ge 0并且y\ge 0\\ \therefore k\in[\lceil -x_0\times \frac{d}{b}\rceil,\lfloor y_0\times \frac{d}{a}\rfloor]且k\in N x0y0k[x0×bd,y0×ad]kN
首先判断是否有解
然后:
ans=p1x+p2y=p1(x0+k×bd)+p2(y0−k×ad)=(bp1−ap2d)k+p1x0+p2y0 \begin{aligned} ans &amp;=p_1x+p_2y\\ &amp;=p_1(x_0+k\times \frac{b}{d})+p_2(y_0-k\times \frac{a}{d})\\ &amp;=(\frac{bp_1-ap_2}{d})k+p_1x_0+p_2y_0 \end{aligned} ans=p1x+p2y=p1(x0+k×db)+p2(y0k×da)=(dbp1ap2)k+p1x0+p2y0
然后便是一个一次函数在区间上求最值的问题

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b,p1,p2,n;

ll gcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0){x=1,y=0;return a;}
	ll d=gcd(b,a%b,x,y);
	ll tmp=x;
	x=y;
	y=tmp-a/b*y;
	return d;
}
int main()
{
	cin>>n;
	cin>>p1>>a>>p2>>b;
	ll x,y;
	ll d=gcd(a,b,x,y);
	if(n%d!=0){puts("-1");return 0;}
	x*=n/d;
	y*=n/d;
	ll m1=ceil((double)-x*d/b),m2=floor((double)y/a*d);
	if(m2<m1){puts("-1");return 0;}
	ll ans=p1*x+p2*y;
	ll k=(p1*b-p2*a)/d;
	if(k<0)ans+=k*m2;
	if(k>0)ans+=k*m1;
	cout<<ans;
	return 0;
}

注:注意斜率以及自变量的范围

E 越狱(NKOJ3950)(NKOJ3950)(NKOJ3950)

监狱有连续编号为1...N1...N1...NNNN个房间,每个房间关押一个犯人,有MMM种宗教,每个犯人可能信仰其中一种。如果
相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱

输入两个整数M,N.1&lt;=M&lt;=108,1&lt;=N&lt;=1012M,N.1&lt;=M&lt;=10^8,1&lt;=N&lt;=10^{12}MN.1<=M<=108,1<=N<=1012

这道题从正面不是很好考虑。
所以我们从反面来思考
对于每一个房间都有MMM种信仰可以选择
所以总的方案数为:S=NMS=N^MS=NM
在这总的方案数里,有多少是不发生冲突的呢?
对于第一个房间,他有MMM种信仰可以选择
对于之后的每个房间,他一定不能选与之前的房间相同的信仰,即只有M−1M-1M1种选择
所以不发生冲突的方案有:T=M×(M−1)N−1T=M \times(M-1)^{N-1}T=M×(M1)N1
所以最后的答案是ans=S−Tans=S-Tans=ST

#include<bits/stdc++.h>
using namespace std;

const int p=100003;
typedef long long ll;
ll mog(ll a,ll b)
{
	a%=p;
	ll ans=1;
	while(b)
	{
		if(b&1){ans=(ans*a)%p;}
		b>>=1;
		a=(a*a)%p;
	}
	return ans;
}

int main()
{
	long long m,n;
	cin>>m>>n;
	cout<<((mog(m,n)-(m%p)*mog(m-1,n-1))%p+p)%p;
	return 0;
}

注:注意数据大小,避免溢出

E.看电影(NKOJ3212)(NKOJ 3212 )(NKOJ3212)

何老板获得了一张电影院的免费观影卡,可以免费连续看LLL分钟的电影。该影院有N部电影可供选择,每部电影会在当天的不同时段放映。

何老板可以在一部电影播放过程中的任何时间进入或退出放映厅。但他不愿意重复看到一部电影,所以每部电影他最多看一次。他也不能在看一部电影的过程中,换到另一个正在播放相同电影的放映厅。

请帮何老板计算他能否做到从000LLL分钟连续不断地观看电影,如果能,请计算他最少看几部电影就行了(一部电影只要看了,即使没有看完也算看了该电影)。
1&lt;=N&lt;=20K&lt;=10001&lt;=L&lt;=100,000,000 1 &lt;= N &lt;= 20\\ K&lt;=1000\\ 1 &lt;= L &lt;= 100,000,000 1<=N<=20K<=10001<=L<=100,000,000
我们立即注意到:这道题的NNN取值非常小,再加同一部的电影只能看一次,所以我们很自然地就想到了状态压缩+DPDPDP
f[s]f[s]f[s]表示把s集合中的电影看完所达到的最长时间
不难得出方程:
f[s]=max⁡{begin[i]+time[i]}begin[i]表示距离f[s0](s0是s集合中除去第i部电影的集合)最近的第i部电影的放映开始时间ans=min⁡{f[s]中1的数量∣f[s]≥L} f[s]=\max\{begin[i]+time[i]\} \\begin[i]表示距离f[s_0](s_0是s集合中除去第i部电影的集合)最近的第i部电影的放映开始时间 \\ans=\min\{f[s]中1 的数量|f[s]\ge L\} f[s]=max{begin[i]+time[i]}begin[i]f[s0](s0si)ians=min{f[s]1f[s]L}

#include<stdio.h>
#include<string>
#include<algorithm>
using namespace std;
#define lowbit(x) (x&-x)
typedef long long ll;
int t[25];
int ss[25][1005];
int cnt[25];
int n,l;
int logg[1<<21];
int f[1<<21];
int main()
{
	//freopen("data.in","r",stdin);
	scanf("%d%d",&n,&l);
	for(int i=1;i<=n;i++)
	{
		logg[1<<i]=i;
		scanf("%d%d",&t[i],&cnt[i]);
		for(int j=1;j<=cnt[i];j++)scanf("%d",&ss[i][j]);
	}
	int tot=(1<<n)-1;
	int ans=99;
	for(int s=0;s<=tot;s++)
	{
		int cnt1=0;
		for(int s1=s,k=lowbit(s1),s0=s^k,i=logg[k]+1;s1;s1^=k,k=lowbit(s1),s0=s^k,i=logg[k]+1)
		{
			cnt1++;
			int tmp=upper_bound(ss[i]+1,ss[i]+1+cnt[i],f[s0])-ss[i]-1;
			if(f[s]<t[i]+ss[i][tmp])f[s]=t[i]+ss[i][tmp];
		}
		if(f[s]>=l&&ans>cnt1)ans=cnt1;
	}
	if(ans==99)puts("-1");
	else printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值