关于2019NKOJ4月月赛

NKOJ2019四月月赛

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

A.切火腿肠( N K O J 4737 NKOJ4737 NKOJ4737)

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

解:

假设每根火腿肠长度为 L L L,则总长度为 S = N × L S=N \times L S=N×L
所以每个人能够分到的长度为: a v e r g e = S M = N × L M \\ averge=\frac{S}{M}=\frac{N \times L}{M} averge=MS=MN×L
一根长度为S的火腿需要切:
S a v e r g e = N × L N × L M = M \frac{S}{averge}=\frac{N \times L}{\frac{N \times L}{M}}=M avergeS=MN×LN×L=M
此时考虑某一个切点恰好是位于两根火腿之间的
设最少第 T T T刀在两根火腿之间,则:
k × L = a v e r g e × T = N × L × T M ⟹ T = k × M N 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 = l c m ( M , N ) N \therefore T=\frac{lcm(M,N)}{N} T=Nlcm(M,N)
∴ a n s = M − M T = M − M l c m ( M , N ) N = M − g c d ( 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.翻硬币( N K O J 5590 NKOJ5590 NKOJ5590)

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

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

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

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

#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.三分数组 ( N K O J 3049 ) (NKOJ3049) (NKOJ3049)

给出一个有 n n n个整数的数组 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an, 有多少种方法把数组分成 3 3 3个连续的子序列,使得各子序列的元素之和相等。也就是说,有多少个下标对 i , j ( 2 ≤ i ≤ j ≤ n − 1 ) , i,j (2≤i≤j≤n-1), i,j(2ijn1),满足: s u m ( a 1 . . a i − 1 ) = s u m ( a i . . a j ) = s u m ( a j + 1 . . a n ) 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)
1 1 1 行: 1 1 1 个整数 n ( 1 &lt; = n &lt; = 5 ∗ 1 0 5 ) n(1 &lt;= n &lt;= 5*10^5) n(1<=n<=5105)
接下来n 行,每行1 个整数,表示 a i ( ∣ a i ∣ &lt; = 1 0 9 ) a_i( |a_i| &lt;= 10^9) ai(ai<=109)

首先先前缀和处理,然后再判断 3 ∣ s u m [ n ] 3|_{sum[n]} 3sum[n],若不整除,则无解。如果整除:
定义 a v e r g e = s u m [ n ] 3 averge=\frac{sum[n]}{3} averge=3sum[n]
考虑:
对于每一个 s u m [ j ] = a v e r g e × 2 sum[j]=averge\times 2 sum[j]=averge×2的位置,它能组成的下标对的个数就是在它前面的 s u m [ i ] = a v e r g e sum[i]=averge sum[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.观光车 ( N K O J 3677 ) (NKOJ3677) (NKOJ3677)

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

景区提供两种观光车,一种是每辆车可以坐 a a a名游客,包一辆车费用是 p 1 p_1 p1块钱;另一种每辆车可以坐b名游客,包一辆车费用是 p 2 p_2 p2块钱。

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

设坐 x x x辆价格为 p 1 p_1 p1的车, y y y辆价格为 p 2 p_2 p2的车
首先先解出不定方程
a x + b y = n ax+by=n ax+by=n
先判断是否有解,若有解:
求:
a n s = p 1 x + p 2 y ( x ≥ 0 , y ≥ 0 ) 的 最 大 值 ans=p_1 x+p_2 y (x\ge 0,y\ge 0)的最大值 ans=p1x+p2y(x0,y0)
扩展欧几里得算法可以得出:
{ x = x 0 + k × b d y = y 0 − k × a d \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 ∈ [ ⌈ − x 0 × d b ⌉ , ⌊ y 0 × d a ⌋ ] 且 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
首先判断是否有解
然后:
a n s = p 1 x + p 2 y = p 1 ( x 0 + k × b d ) + p 2 ( y 0 − k × a d ) = ( b p 1 − a p 2 d ) k + p 1 x 0 + p 2 y 0 \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 越狱 ( N K O J 3950 ) (NKOJ3950) (NKOJ3950)

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

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

这道题从正面不是很好考虑。
所以我们从反面来思考
对于每一个房间都有 M M M种信仰可以选择
所以总的方案数为: S = N M S=N^M S=NM
在这总的方案数里,有多少是不发生冲突的呢?
对于第一个房间,他有 M M M种信仰可以选择
对于之后的每个房间,他一定不能选与之前的房间相同的信仰,即只有 M − 1 M-1 M1种选择
所以不发生冲突的方案有: T = M × ( M − 1 ) N − 1 T=M \times(M-1)^{N-1} T=M×(M1)N1
所以最后的答案是 a n s = S − T ans=S-T ans=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.看电影 ( N K O J 3212 ) (NKOJ 3212 ) (NKOJ3212)

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

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

请帮何老板计算他能否做到从 0 0 0 L L L分钟连续不断地观看电影,如果能,请计算他最少看几部电影就行了(一部电影只要看了,即使没有看完也算看了该电影)。
1 &lt; = N &lt; = 20 K &lt; = 1000 1 &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
我们立即注意到:这道题的 N N N取值非常小,再加同一部的电影只能看一次,所以我们很自然地就想到了状态压缩+ D P DP DP
f [ s ] f[s] f[s]表示把s集合中的电影看完所达到的最长时间
不难得出方程:
f [ s ] = max ⁡ { b e g i n [ i ] + t i m e [ i ] } b e g i n [ i ] 表 示 距 离 f [ s 0 ] ( s 0 是 s 集 合 中 除 去 第 i 部 电 影 的 集 合 ) 最 近 的 第 i 部 电 影 的 放 映 开 始 时 间 a n s = 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、付费专栏及课程。

余额充值