A. Farmer John's Challenge
构建循环右移有序数组,有以下三种情况:
1、k=n时,我们使n个数都相等即可;
2、k=1时,除了都相等的情况外随意构建即可;
3、其他情况无解,因为除了n个数都相等的情况下,一个有序数组的最左端的数一定比最右端的数小,无论如何循环右移都无法构成第二个有序数组。
void solve()
{
int n,k; cin>>n>>k;
if(k==1)
{
for(int i=1;i<=n;i++) cout<<i<<" "; cout<<"\n";
}
else if(k==n)
{
for(int i=1;i<=n;i++) cout<<"1 "; cout<<"\n";
}
else cout<<"-1\n";
}
B. Bessie and MEX
ai = MEX(p1,p2,…,pi)−pi,即a数组每一个数都和他的MEX前缀有关系,所以我们考虑从后往前跑出答案,由于p是一个排列,对于pn的结果我们是可以确定的即pn = n-an,然后我们根据后一个数的确定从而更新前一个数的前缀MEX即可。
int a[N],p[N];
void solve()
{
int n,i; cin>>n;
for(i=1;i<=n;i++) cin>>a[i];
int cnt=n; //当前的MEX
for(i=n;i>=1;i--)
{
p[i]=cnt-a[i];
cnt=min(cnt,p[i]);
}
for(i=1;i<=n;i++) cout<<p[i]<<" "; cout<<"\n";
}
C1. Bessie's Birthday Cake (Easy Version)
对于一个n边形随意连边我们可以切割出n-2个三角形,所以题目给出的x个可连接顶点所构成的x边形我们可以把它单独拿出来计算出一部分结果,同时当切割点的两个相邻坐标距离为2时(例如1,3)可以额外切割出一个三角形(1,3,2),将这部分结果加上即可。
int a[N];
void solve()
{
int n,m,y,i; cin>>n>>m>>y; //m为题目内的x
for(i=1;i<=m;i++) cin>>a[i];
sort(a+1,a+1+m);
int ans=m-2;
for(i=1;i<m;i++)
if(a[i+1]-a[i]==2) ans++;
if(a[1]+n-a[m]==2) ans++; //判断首尾距离
cout<<ans<<"\n";
}
C2. Bessie's Birthday Cake (Hard Version)
这题与C1相比多了一个贪心策略:
对于两个相邻间隔大于2的点,我们在中间添加一个点可以得到的新三角形至少为两个(内部x边形变为x+1边形,新加的点与旁边的点距离恰好为2),如果新添加的点与两边的点距离都为2,那么我们可以得到3个新三角形(例如1,5中间添加3);
当两个相邻的点间隔距离小于2时,我们无法添加;
等于2时,我们添加会破坏原有的外部三角形;
所以我们需要将相邻距离是奇数的值按大小存起来(最后一次添加点可以得到3个新三角形),偶数的正常添加即可。
int a[N];
void solve()
{
int n,m,y,i; cin>>n>>m>>y; //m为题目内的x
for(i=1;i<=m;i++) cin>>a[i];
sort(a+1,a+1+m);
int ans=m-2;
for(i=1;i<m;i++)
if(a[i+1]-a[i]==2) ans++;
if(a[1]+n-a[m]==2) ans++;
priority_queue<int,vector<int>,greater<int>>q;
//优先队列优先访问距离为偶数的,这样可以更快的得到一步切割三个点的操作
int cnt=0;
for(i=1;i<m;i++)
{
int d=a[i+1]-a[i];
if(d<=2) continue;
if(d%2) cnt+=d/2;
else q.push(d/2-1); //(1,5)中间可以添加一个点
}
int d=a[1]+n-a[m];
if(d%2&&d>2) cnt+=d/2;
else if(d>2) q.push(d/2-1);
while(y&&!q.empty())
{
int t=q.top(); q.pop();
if(t<=y)
{
y-=t;
ans+=2ll*t+1;
}
else
{
ans+=2ll*y; y=0;
break;
}
}
ans+=min(cnt*2,y*2);
cout<<ans<<"\n";
}
D. Learning to Paint
我们考虑开二维dp数组,dp[i][j]表示以i为结尾(不一定要选择i)第k大的数。
所以dp[i][1]=MAX(dp[1][1]+a[3][i],dp[1][2]+a[3][i]...dp[i-1][k])(一定要全选a[3][i]的原因是,只选部分的最大值存储在在后续的dp中),同时因为dp[1][1]是以1为结尾最大值,我们可以优化为:
dp[i][1]=MAX(dp[1][1]+a[3][i],dp[2][1]+a[4][i]...dp[i-1][1]),这样我们可以使用优先队列维护前(i-1)维没有被继承过的最大值(若dp[1][1]被继承过则塞入dp[1][2]),维护出第i维前k个最大即可。
int a[N][N],dp[N][M]; //dp[i][j]表示以i为结尾第j大的数
struct node
{
int val,idx,now; //权值val,以idx结尾,第now大
bool operator < (const node &x) const{
return val < x.val;
}
};
void solve()
{
int n,k,i,j; cin>>n>>k;
for(i=0;i<=n;i++)
for(j=0;j<=k;j++)
dp[i][j]=-inff,a[i][j]=0;
dp[0][1]=0; //什么都不选时的最大值
for(i=1;i<=n;i++)
for(j=i;j<=n;j++)
cin>>a[i][j];
for(i=1;i<=n;i++)
{
priority_queue<node>q;
for(j=i-1;j>=0;j--)
{
int val=a[j+2][i]+dp[j][1];
q.push({val,j,1});
}
q.push({a[1][i],-1,0}); //相当于从第0维继承
int cnt=1;
while(!q.empty())
{
auto t=q.top(); q.pop();
int val=t.val;
int idx=t.idx;
int now=t.now;
if(now>k||cnt>k) continue;
if(idx==-1)
{
dp[i][cnt++]=val;
continue;
}
else
{
dp[i][cnt++]=val;
q.push({dp[idx][now+1]+a[idx+2][i],idx,now+1});
}
}
}
for(i=1;i<=k;i++) cout<<dp[n][i]<<" "; cout<<"\n";
}
E. Farm Game
由题可知,若(ai,bi)(1≤i≤n)的距离均为0,那么先手必败,因为后手可以模仿先手的行进直至无法行动,对应的,我们可以扩展到距离均为偶数必败,同样也是模仿直至先手被逼到死角后往回走,后手下一步继续逼近(距离-2),直至距离为0。
所以我们求出总方案数 - 距离均为偶数的情况再乘以2即可(a牛和b牛调换位置)。
我们可以将两牛空隙视为桶,将其他空的点视为球,n个相同的球塞进m个不同的桶(桶可空)的方法为C(n+m−1,m−1)。
#define int long long
void solve()
{
int l,m; cin>>l>>m;
int n=l-m*2,i;
int ans=cmb(n+m*2,m*2); //cmb(n,m)为n里取m
for(i=0;i<=n;i+=2)
{
int now=n-i;
int cnt=cmb(i/2ll+m-1,m-1)*cmb(now+m,m)%M;
//将两个数当一个数塞进空隙,使两牛间隔固定为偶数
ans=((ans-cnt)%M+M)%M;
}
cout<<ans*2ll%M<<"\n";
}
F. Farmer John's Favorite Function
这道题当一个数最多计算6次后第一个数对最后结果的影响就不会超过1(1e18开六次根的结果为1.91)。
我们可以分块,每块的答案在前缀答案不同的情况下只会有两种情况(ans和ans+1),所以我们只需要处理出每一块需要得到ans+1情况的前缀最小值即可。
对于每次修改,都修改该点所在块的结果,遍历每一块并跑完剩余没有分块的点即可。
#define int __int128
#define ll long long
int read() {int w = 0, h = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-')h = -h;ch = getchar();}while (ch >= '0' && ch <= '9'){w = w * 10 + ch - '0';ch = getchar();}return w * h;}
void write(int x) { if (x > 9) write(x / 10); putchar(x % 10 + '0');}
void writeln(int x) { write(x); puts(""); }
void writech(int x) { write(x); putchar(' '); }
int a[N],t[N],mi[N],id[N],L[N],R[N];
//t为跑出该块的结果,mi为进入该块跑出结果为t+1的最小值
int n,q,len;
int sqrtt(int k)
{
int x=sqrt((ll)k);
while((x+1)*(x+1)<k) x++;
while((x)*(x)>k) x--;
return x;
}
void build(int cnt)
{
int i,x=0;
int l=L[cnt],r=R[cnt];
for(i=l;i<=r;i++)
{
id[i]=cnt;
x=sqrtt(x+a[i]);
}
t[cnt]=x; x++;
for(i=r;i>=l;i--)
{
x=x*x-a[i];
if(x>=inff) break;
}
mi[cnt]=x;
}
void solve()
{
int i,j,cnt=0; n=read(); q=read();
for(i=1;i<=n;i++) a[i]=read();
len=min((int)100,n);
for(i=1;i+len<=n;i+=len)
{
cnt++;
L[cnt]=i; R[cnt]=i+len-1;
build(cnt);
}
while(q--)
{
int k,x,ans=0;
k=read(); x=read(); a[k]=x;
if(id[k]) build(id[k]);
for(i=1;i<=cnt;i++)
{
if(ans>=mi[i]) ans=t[i]+1;
else ans=t[i];
}
for(i=R[cnt]+1;i<=n;i++) ans=sqrtt(ans+a[i]);
writeln(ans);
}
}