T1 玩游戏
这是一道博弈论题,但是数据比较大,于是只能使用打表找规律大法。
把每个房间分开来看,先打出 a i ≤ 1000 a_i \leq 1000 ai≤1000左右的表,然后就发现 a i m o d 4 = 0 a_i \bmod 4 =0 aimod4=0时后手必胜,其余情况先手必胜。
我们来简单证明一下。
当 a i m o d 4 = 0 a_i \bmod 4 = 0 aimod4=0时,由于任何质数(包括 1 1 1)模 4 4 4的余数都不为 0 0 0,所以取走任意合法个数后,后手都有办法将这个数重新变为 4 4 4的倍数。
其余情况,先手可以取走一个数将当前数变为 4 4 4的倍数。
接下来把所有房间合起来看。
作为先手,需要让必胜态尽量快点被取完,所以就减去一个尽量大的质数使得这个数变为 4 4 4的倍数。轮数则为 ( x − p ) / 4 + 1 (x-p)/4+1 (x−p)/4+1, p p p为那个尽量大的质数, x x x则为当前个数。
而必败态则拖得越久越好,先手每次取 2 2 2,而后手则只能取走 2 2 2让这个数变回 4 4 4的倍数。轮数则为 x / 4 + 1 x/4+1 x/4+1。
具体实现看代码。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN=100005;
const LL MAXM=5000005;
LL t;
LL n,a[MAXN];
LL p[MAXM],cnt;
bool prime[MAXM];
LL ans[MAXM],pri[5];
int main(){
scanf("%lld",&t);
p[++cnt]=1;
for(LL i=2;i<=5000000;i++){
if(!prime[i]){
p[++cnt]=i;
for(LL j=i+i;j<=5000000;j+=i){
prime[j]=true;
}
}
}
for(LL i=0;i<=5000000;i+=4){
ans[i]=i/4+1;
}
for(LL i=1;i<=5000000;i++){
if(i%4==0)continue;
if(!prime[i]){
pri[i%4]=i;
}
ans[i]=(i-pri[i%4])/4+1;
}
while(t--){
scanf("%lld",&n);
memset(a,0,sizeof(a));
for(LL i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
LL minn=LONG_LONG_MAX,minx;
for(LL i=1;i<=n;i++){
if(minn>ans[a[i]]){
minn=ans[a[i]];
minx=a[i];
}
}
if(minx%4==0)printf("Farmer Nhoj\n");
else printf("Farmer John\n");
}
}
T2 看电影
这道题看起来很像背包,但是仔细一分析发现正常做法无法
A
C
AC
AC。于是我们就需要一个不正常的做法。
可以发现我们需要让冰激凌尽量被花费在 x i x_i xi较小的人身上,因为在他身上冰激凌更值钱。
所以就先按照 x i x_i xi的大小排序,从前往后做一次背包,不考虑钱,只考虑冰激凌的花费。
而钱主要是被花费在冰激凌不值钱的人身上,所以从后往前做一次背包,只考虑金钱。
这时候会出现两种情况,第一种是有一个人,既用冰激凌又用钱,第二种是前面的一部分用冰激凌,后面的用钱。
如何确定这个分界线呢?答案是枚举,同时找到最大的欢迎程度。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN=2005;
LL n,a,b;
struct node{
LL p,c,x;
bool operator <(const node &T)const{
return x<T.x;
}
}t[MAXN];
LL f[MAXN][MAXN];
LL g[MAXN][MAXN];
int main(){
scanf("%lld%lld%lld",&n,&a,&b);
for(LL i=1;i<=n;i++){
scanf("%lld%lld%lld",&t[i].p,&t[i].c,&t[i].x);
}
sort(t+1,t+n+1);
for(LL i=1;i<=n;i++){
for(LL j=0;j<=b;j++)f[i][j]=f[i-1][j];
for(LL j=b;j>=t[i].x*t[i].c;j--)f[i][j]=max(f[i][j],f[i-1][j-t[i].x*t[i].c]+t[i].p);
}
for(LL i=n;i>=1;i--){
for(LL j=0;j<=a;j++)g[i][j]=g[i+1][j];
for(LL j=a;j>=t[i].c;j--)g[i][j]=max(g[i][j],g[i+1][j-t[i].c]+t[i].p);
}
LL ans=0;
for(LL i=1;i<=n;i++){
ans=max(ans,f[i-1][b]+g[i][a]);
ans=max(ans,f[i][b]+g[i+1][a]);
for(LL j=1;j*t[i].x<=min(b,t[i].c*t[i].x);j++){
if(a-t[i].c+j<0)continue;
ans=max(ans,f[i-1][b-t[i].x*j]+g[i+1][a-t[i].c+j]+t[i].p);
}
}
printf("%lld",ans);
}
T3 山峰
这道题考虑暴力用 n n n个 s e t set set维护每个山能看见的右边的山。
初始时直接暴力维护。
每次修改时,可以直接重构该点的 s e t set set,以达到更新右侧的效果。而左侧也是枚举。
由于最终每个 s e t set set中的值都是上升的,所以我们在判断它能否插入时,只需要与左边的第一个比较就行了。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pi pair<LL,double>
const LL MAXN=2005;
LL n,a[MAXN];
LL q;
set<pi>b[MAXN];
int main(){
scanf("%lld",&n);
for(LL i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(LL i=1;i<=n;i++){
for(LL j=i+1;j<=n;j++){
double t=double(a[j]-a[i])/double(j-i);
if(b[i].empty()||(*prev(b[i].end())).second<=t){
b[i].insert({j,t});
}
}
}
scanf("%lld",&q);
while(q--){
LL x,y;
scanf("%lld%lld",&x,&y);
a[x]+=y;
b[x].clear();
for(LL i=x+1;i<=n;i++){
double t=double(a[i]-a[x])/double(i-x);
if(b[x].empty()||(*prev(b[x].end())).second<=t){
b[x].insert({i,t});
}
}
for(LL i=1;i<x;i++){
double t=double(a[x]-a[i])/double(x-i);
auto it=b[i].lower_bound({x,t});
if(it!=b[i].begin()){
pi temp=(*(--it));
if(temp.second>t){
continue;
}
}
it=b[i].lower_bound({x,-1000000000});
if((*it).first==x){
b[i].erase(*it);
}
b[i].insert({x,t});
it=b[i].upper_bound({x,t});
while(it!=b[i].end()){
if(t<=(*it).second)break;
LL fi=(*it).first,se=(*it).second;
b[i].erase((*it));
it=b[i].upper_bound({fi,se});
}
}
LL ans=0;
for(LL i=1;i<=n;i++){
ans+=b[i].size();
}
printf("%lld\n",ans);
}
}