A.
如果以
7
7
7为一个周期,可以知道我们可以打
9
9
9点伤害。所以要想完美过关,怪的生命值和一定是
9
9
9的倍数。同时我们可以算出需要
s
u
m
/
9
sum/9
sum/9个周期才能解决,因此每一个怪的生命值都要大于等于
s
u
m
/
9
sum/9
sum/9。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int a,b,c,T;
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d%d",&a,&b,&c);
int sum=a+b+c;
if ((sum%9==0) && (a>=sum/9) && (b>=sum/9) && (c>=sum/9)) printf("YES\n");
else printf("NO\n");
}
}
B.
我们可以这样构造数列,
1
,
a
2
,
1
,
a
4
,
.
.
.
.
1,a_2,1,a_4,....
1,a2,1,a4,....
或者
a
1
,
1
,
a
3
,
1
,
.
.
.
.
a_1,1,a_3,1,....
a1,1,a3,1,....
显然这样的权值就是
a
1
−
1
+
a
3
−
1
+
.
.
.
a_1-1+a_3-1+...
a1−1+a3−1+...,或者
a
2
−
1
+
a
4
−
1
+
.
.
.
a_2-1+a_4-1+...
a2−1+a4−1+...
显然有一个权值小于
S
S
S的一半。
代码:
#include <iostream>
#include <cstdio>
#define LL long long
const int maxn=107;
using namespace std;
int T,n;
LL sum,l,r;
LL a[maxn];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
sum=l=r=0;
for (int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
}
for (int i=1;i<=n;i++)
{
if (i&1) l+=a[i];
else r+=a[i];
}
if (l<=sum/2)
{
for (int i=1;i<=n;i++)
{
if (i&1) printf("1 ");
else printf("%lld ",a[i]);
}
}
else
{
for (int i=1;i<=n;i++)
{
if (i&1) printf("%lld ",a[i]);
else printf("1 ");
}
}
printf("\n");
}
}
C.
我们可以记录当前的位置
o
p
op
op以及目标位置
e
d
ed
ed。
如果在时刻
T
i
T_i
Ti,
o
p
=
e
d
op=ed
op=ed,那么就会执行命令
i
i
i。
并处理出时刻
T
i
+
1
T_{i+1}
Ti+1所在位置
d
d
d。如果
x
i
x_i
xi位于
o
p
op
op与
d
d
d之间,这说明这个命令有效。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
const int maxn=1e5+7;
using namespace std;
int T,n;
LL op,ed,ans;
LL a[maxn],b[maxn];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]);
op=0,ed=0,ans=0;
a[n+1]=2e10+1;
for (int i=1;i<=n;i++)
{
if (op==ed) ed=b[i];
LL l=op,r;
if (ed<op)
{
if (op-ed<=a[i+1]-a[i]) op=ed;
else op-=a[i+1]-a[i];
}
else
{
if (ed-op<=a[i+1]-a[i]) op=ed;
else op+=a[i+1]-a[i];
}
r=op;
if (l>r) swap(l,r);
if ((l<=b[i]) && (b[i]<=r)) ans++;
}
printf("%lld\n",ans);
}
}
D.
考虑最少需要选多少个最小值与最大值。
以最小值为例,如果某一个数
x
x
x,
c
n
t
cnt
cnt为小于
x
x
x且没有出现在序列中并且没有匹配的数的个数。
显然如果
a
a
a在序列中且
c
n
t
cnt
cnt不为
0
0
0,则
c
n
t
−
1
cnt-1
cnt−1;
如果
a
a
a在序列中且
c
n
t
cnt
cnt为
0
0
0,此时必须取一个最小值,另一个数可以选择最大的没有出现的数(如果被选了就次大…);
如果
a
a
a不存在则
c
n
t
+
1
cnt+1
cnt+1;
那么只要满足最少的最小值与最大值个数的方案都是合法的。
代码:
#include <iostream>
#include <cstdio>
const int maxn=2e5+7;
using namespace std;
int T,n,sum,l,r;
int a[maxn];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
a[n+1]=2*n+1;
l=0,r=0;
sum=0;
for (int i=1;i<=n;i++)
{
sum+=a[i]-a[i-1]-1;
if (!sum) l++;
else sum--;
}
sum=0;
for (int i=n;i>0;i--)
{
sum+=a[i+1]-a[i]-1;
if (!sum) r++;
else sum--;
}
printf("%d\n",n-r-l+1);
}
}
E.
首先我们考虑第二类限制,显然只有当限制是若干条链的情况才是合法的。
我们可以把每条链缩成一个点,可以用并查集维护,集合的根设为链的开头。
对于第一类限制,我们设
(
x
,
y
)
(x,y)
(x,y)表示
x
x
x是
y
y
y的前置条件。
则如果
x
x
x与
y
y
y属于不同的链,那么我们从
y
y
y的链头向
x
x
x的链头连边。表示
x
x
x的链是
y
y
y的链的前置条件,因为每条链必须一次连续做完。
而如果
x
x
x与
y
y
y处于同一条链,则判断
x
x
x在链上的顺序是否在
y
y
y前面即可,不需要连边。
然后对新建的图跑top序,如果存在则有解。
代码:
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
const int maxn=3e5+7;
using namespace std;
int n,m,x,y,cnt;
int b[maxn],r[maxn],last[maxn],to[maxn],dep[maxn],v[maxn],p[maxn],ans[maxn];
vector <int> a[maxn];
queue <int> q;
int find(int x)
{
if (!p[x]) return x;
return p[x]=find(p[x]);
}
void uni(int x,int y)
{
int u=find(x),v=find(y);
if (u!=v) p[u]=v;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
b[i]=x;
}
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
if ((!last[y]) && (find(x)!=find(y)))
{
last[y]=x;
to[x]=y;
uni(y,x);
}
else
{
printf("0\n");
return 0;
}
}
for (int i=1;i<=n;i++)
{
if (!last[i])
{
int x=i;
while (x)
{
dep[x]=dep[last[x]]+1;
x=to[x];
}
}
}
for (int i=1;i<=n;i++)
{
int y=b[i];
if (!y) continue;
if (find(i)!=find(y))
{
a[find(y)].push_back(find(i));
r[find(i)]++;
}
else
{
if (dep[y]>dep[i])
{
printf("0\n");
return 0;
}
}
}
for (int i=1;i<=n;i++)
{
if ((!p[i]) && (!r[i]))
{
q.push(i);
v[i]=1;
ans[++cnt]=i;
}
}
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=0;i<a[x].size();i++)
{
int y=a[x][i];
r[y]--;
if ((!r[y]) && (!v[y]))
{
v[y]=1;
q.push(y);
ans[++cnt]=y;
}
}
}
for (int i=1;i<=n;i++)
{
if ((!p[i]) && (!v[i]))
{
printf("0\n");
return 0;
}
}
for (int i=1;i<=cnt;i++)
{
int x=ans[i];
while (x)
{
printf("%d ",x);
x=to[x];
}
}
}
F.
我们可以从
[
1
,
x
+
y
]
[1,x+y]
[1,x+y]选择一些数满足条件。
对于每一个选择的数
i
i
i,我们选上所有
i
≡
j
(
m
o
d
(
x
+
y
)
)
(
j
<
=
n
)
i≡j(mod\ (x+y))(j<=n)
i≡j(mod (x+y))(j<=n),这样构造出的集合是合法的。
证明:显然在同一个区间内选的数一定是合法的,而如果跨区间。
如果不合法则满足
∣
p
+
(
x
+
y
)
−
q
∣
=
x
|p+(x+y)-q|=x
∣p+(x+y)−q∣=x或者
∣
p
+
(
x
+
y
)
−
q
∣
=
y
|p+(x+y)-q|=y
∣p+(x+y)−q∣=y,
化简得到
∣
p
−
q
∣
=
y
|p-q|=y
∣p−q∣=y,或者
∣
p
−
q
∣
=
x
|p-q|=x
∣p−q∣=x,与我们假设的区间内合法矛盾。
至于为什么这样构造出的集合 s i z e size size可以是最大的,可以参考cf的题解。
那么我们只需要考虑
[
1
,
x
+
y
]
[1,x+y]
[1,x+y]的每个数选还是不选即可,设
f
[
i
]
[
s
t
a
]
f[i][sta]
f[i][sta]表示前
i
i
i个数,选的数状态为
s
t
a
sta
sta的集合大小最大值。
维护状态
s
t
a
sta
sta可以知道
i
i
i能不能选,如果选了
i
i
i,集合增加的大小就是
i
≡
j
(
m
o
d
(
x
+
y
)
)
(
j
<
=
n
)
i≡j(mod\ (x+y))(j<=n)
i≡j(mod (x+y))(j<=n)。
我们可以令
x
>
y
x>y
x>y,那么
[
x
+
1
,
y
]
[x+1,y]
[x+1,y]的状态我们不需要记录,因为他们不会影响后面的转移。
所以复杂度就是
O
(
(
x
+
y
)
∗
2
m
a
x
(
x
,
y
)
)
O((x+y)*2^{max(x,y)})
O((x+y)∗2max(x,y))。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
const int maxn=4200007;
using namespace std;
int n,x,y,p,l,r,ans;
int f[2][maxn],bit[25];
int main()
{
scanf("%d%d%d",&n,&x,&y);
if (x<y) swap(x,y);
p=x+y;
l=n/(x+y);
r=n%(x+y);
bit[0]=1;
for (int i=1;i<=x;i++) bit[i]=bit[i-1]*2;
int sta=0;
for (int j=0;j<bit[x];j++) f[sta][j]=-0x3f3f3f3f;
f[sta][0]=0;
for (int i=1;i<=x+y;i++)
{
sta=1-sta;
for (int j=0;j<bit[x];j++) f[sta][j]=f[1-sta][j];
for (int j=0;j<bit[x];j++)
{
if ((i-y>=1) && (j&bit[i-y-1])) continue;
if ((i-x>=1) && (j&bit[i-x-1])) continue;
if (i<=x) f[sta][j|bit[i-1]]=max(f[sta][j|bit[i-1]],f[1-sta][j]+l+(i<=r));
else f[sta][j]=max(f[sta][j],f[1-sta][j]+l+(i<=r));
}
}
ans=0;
for (int j=0;j<bit[x];j++) ans=max(ans,f[sta][j]);
printf("%d\n",ans);
}