NKOJ2019四月月赛
这场比赛打得是心态爆炸。该拿的分没拿完。哎,自己是真的菜。
A.切火腿肠(NKOJ4737NKOJ4737NKOJ4737)
何老板有N根大小相同且质地均匀的火腿肠,要分给M名信竞队员。要求每名队员分得的香肠重量相同。
何老板想知道,最少切多少刀就能满足上述要求?
一行,两个整数N和M,(1<=N,M<=10001<=N,M<=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×T⟹T=Nk×M
∵T,k∈N∗且要T最小\because T,k\in N^*且要T最小∵T,k∈N∗且要T最小
∴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=M−TM=M−Nlcm(M,N)M=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.翻硬币(NKOJ5590NKOJ5590NKOJ5590)
何老板将nnn个硬币排成一排。从左往右编号111到nnn。有的正面(国徽)朝上,有的背面(面额)朝上。
为方便表示,何老板用字母BBB代表正面朝上,WWW代表背面朝上。于是就得到一个由大写字母BBB和WWW构成的长度为nnn的字符串SSS。
对任意相邻的两个硬币iii和i+1i+1i+1号硬币(1<=i<n)(1<=i<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(2≤i≤j≤n−1),满足: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..ai−1)=sum(ai..aj)=sum(aj+1..an)
第111 行:111 个整数n(1<=n<=5∗105)n(1 <= n <= 5*10^5)n(1<=n<=5∗105)
接下来n 行,每行1 个整数,表示ai(∣ai∣<=109)a_i( |a_i| <= 10^9)ai(∣ai∣<=109)
解
首先先前缀和处理,然后再判断3∣sum[n]3|_{sum[n]}3∣sum[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(x≥0,y≥0)的最大值
扩展欧几里得算法可以得出:
{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=y0−k×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
∵x≥0并且y≥0∴k∈[⌈−x0×bd⌉,⌊y0×ad⌋]且k∈N
首先判断是否有解
然后:
ans=p1x+p2y=p1(x0+k×bd)+p2(y0−k×ad)=(bp1−ap2d)k+p1x0+p2y0
\begin{aligned}
ans &=p_1x+p_2y\\
&=p_1(x_0+k\times \frac{b}{d})+p_2(y_0-k\times \frac{a}{d})\\
&=(\frac{bp_1-ap_2}{d})k+p_1x_0+p_2y_0
\end{aligned}
ans=p1x+p2y=p1(x0+k×db)+p2(y0−k×da)=(dbp1−ap2)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...N的NNN个房间,每个房间关押一个犯人,有MMM种宗教,每个犯人可能信仰其中一种。如果
相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱
输入两个整数M,N.1<=M<=108,1<=N<=1012M,N.1<=M<=10^8,1<=N<=10^{12}M,N.1<=M<=108,1<=N<=1012
解
这道题从正面不是很好考虑。
所以我们从反面来思考
对于每一个房间都有MMM种信仰可以选择
所以总的方案数为:S=NMS=N^MS=NM
在这总的方案数里,有多少是不发生冲突的呢?
对于第一个房间,他有MMM种信仰可以选择
对于之后的每个房间,他一定不能选与之前的房间相同的信仰,即只有M−1M-1M−1种选择
所以不发生冲突的方案有:T=M×(M−1)N−1T=M \times(M-1)^{N-1}T=M×(M−1)N−1
所以最后的答案是ans=S−Tans=S-Tans=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.看电影(NKOJ3212)(NKOJ 3212 )(NKOJ3212)
何老板获得了一张电影院的免费观影卡,可以免费连续看LLL分钟的电影。该影院有N部电影可供选择,每部电影会在当天的不同时段放映。
何老板可以在一部电影播放过程中的任何时间进入或退出放映厅。但他不愿意重复看到一部电影,所以每部电影他最多看一次。他也不能在看一部电影的过程中,换到另一个正在播放相同电影的放映厅。
请帮何老板计算他能否做到从000到LLL分钟连续不断地观看电影,如果能,请计算他最少看几部电影就行了(一部电影只要看了,即使没有看完也算看了该电影)。
1<=N<=20K<=10001<=L<=100,000,000
1 <= N <= 20\\
K<=1000\\
1 <= L <= 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](s0是s集合中除去第i部电影的集合)最近的第i部电影的放映开始时间ans=min{f[s]中1的数量∣f[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;
}