关于2019NKOJ4月月赛

NKOJ2019四月月赛

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

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

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

解:

假设每根火腿肠长度为 L L ,则总长度为 S = N × L S=N \times L
所以每个人能够分到的长度为: a v e r g e = S M = N × L M \\ averge=\frac{S}{M}=\frac{N \times L}{M}
一根长度为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
此时考虑某一个切点恰好是位于两根火腿之间的
设最少第 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}
T , k N T \because T,k\in N^*且要T最小
T = l c m ( M , N ) N \therefore T=\frac{lcm(M,N)}{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)

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

#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 )

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

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

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

其实我们可以贪心一下,如果要进行操作的次数最多,则最好把所有的 B B 都移到右边,那么对于每一个 W W ,则它会被在它左边的每一个 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)

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

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

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

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

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

设坐 x x 辆价格为 p 1 p_1 的车, y y 辆价格为 p 2 p_2 的车
首先先解出不定方程
a x + b y = 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)的最大值
扩展欧几里得算法可以得出:
{ 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 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
首先判断是否有解
然后:
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}
然后便是一个一次函数在区间上求最值的问题

#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)

监狱有连续编号为 1... N 1...N N N 个房间,每个房间关押一个犯人,有 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}

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

#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 )

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

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

请帮何老板计算他能否做到从 0 0 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
我们立即注意到:这道题的 N N 取值非常小,再加同一部的电影只能看一次,所以我们很自然地就想到了状态压缩+ D P DP
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\}

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
typedef long long ll;
ll t[25];
ll ss[25][1005];
ll cnt[25];
ll n,l;
int logg[1<<20];
ll f[1<<20];
int main()
{
    //freopen("data.in","r",stdin);
    scanf("%lld%lld",&n,&l);
    for(int i=1;i<=n;i++)
    {
        logg[1<<i]=i;
        scanf("%lld%lld",&t[i],&cnt[i]);
        for(int j=1;j<=cnt[i];j++)scanf("%lld",&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;
            f[s]=max(f[s],t[i]+ss[i][tmp]);
        }
        if(f[s]>=l)ans=min(ans,cnt1);
    }
    if(ans==99)cout<<-1;
    else cout<<ans;
    return 0;
}

注:这个代码不知为啥只能过大部分的分,其他的分我在洛谷上下数据发现答案是一样的,但是评测显示是WA的。。。。。。

猜你喜欢

转载自blog.csdn.net/qq_35718682/article/details/89428189
今日推荐