NKOJ2019四月月赛
这场比赛打得是心态爆炸。该拿的分没拿完。哎,自己是真的菜。
A.切火腿肠( N K O J 4737 NKOJ4737 NKOJ4737)
何老板有N根大小相同且质地均匀的火腿肠,要分给M名信竞队员。要求每名队员分得的香肠重量相同。
何老板想知道,最少切多少刀就能满足上述要求?
一行,两个整数N和M,( 1 < = N , M < = 1000 1<=N,M<=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×T⟹T=Nk×M
∵
T
,
k
∈
N
∗
且
要
T
最
小
\because T,k\in N^*且要T最小
∵T,k∈N∗且要T最小
∴
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=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.翻硬币( 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 < = i < n ) (1<=i<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(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)
sum(a1..ai−1)=sum(ai..aj)=sum(aj+1..an)
第
1
1
1 行:
1
1
1 个整数
n
(
1
<
=
n
<
=
5
∗
1
0
5
)
n(1 <= n <= 5*10^5)
n(1<=n<=5∗105)
接下来n 行,每行1 个整数,表示
a
i
(
∣
a
i
∣
<
=
1
0
9
)
a_i( |a_i| <= 10^9)
ai(∣ai∣<=109)
解
首先先前缀和处理,然后再判断
3
∣
s
u
m
[
n
]
3|_{sum[n]}
3∣sum[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(x≥0,y≥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=x0+k×dby=y0−k×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
∵x≥0并且y≥0∴k∈[⌈−x0×bd⌉,⌊y0×ad⌋]且k∈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 &=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 越狱 ( N K O J 3950 ) (NKOJ3950) (NKOJ3950)
监狱有连续编号为
1...
N
1...N
1...N的
N
N
N个房间,每个房间关押一个犯人,有
M
M
M种宗教,每个犯人可能信仰其中一种。如果
相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱
输入两个整数 M , N . 1 < = M < = 1 0 8 , 1 < = N < = 1 0 12 M,N.1<=M<=10^8,1<=N<=10^{12} M,N.1<=M<=108,1<=N<=1012
解
这道题从正面不是很好考虑。
所以我们从反面来思考
对于每一个房间都有
M
M
M种信仰可以选择
所以总的方案数为:
S
=
N
M
S=N^M
S=NM
在这总的方案数里,有多少是不发生冲突的呢?
对于第一个房间,他有
M
M
M种信仰可以选择
对于之后的每个房间,他一定不能选与之前的房间相同的信仰,即只有
M
−
1
M-1
M−1种选择
所以不发生冲突的方案有:
T
=
M
×
(
M
−
1
)
N
−
1
T=M \times(M-1)^{N-1}
T=M×(M−1)N−1
所以最后的答案是
a
n
s
=
S
−
T
ans=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 ) (NKOJ3212)
何老板获得了一张电影院的免费观影卡,可以免费连续看 L L L分钟的电影。该影院有N部电影可供选择,每部电影会在当天的不同时段放映。
何老板可以在一部电影播放过程中的任何时间进入或退出放映厅。但他不愿意重复看到一部电影,所以每部电影他最多看一次。他也不能在看一部电影的过程中,换到另一个正在播放相同电影的放映厅。
请帮何老板计算他能否做到从
0
0
0到
L
L
L分钟连续不断地观看电影,如果能,请计算他最少看几部电影就行了(一部电影只要看了,即使没有看完也算看了该电影)。
1
<
=
N
<
=
20
K
<
=
1000
1
<
=
L
<
=
100
,
000
,
000
1 <= N <= 20\\ K<=1000\\ 1 <= L <= 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](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;
}