good-problems 10
1.E. Red-Black Pepper
题意:
题解:
那么我们求出一个关于x的通解,我们只需要三分找到分布在极值两边的x的解即可。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
if(!b){
d=a;x=1;y=0;
return;
}
exgcd(b,a%b,d,x,y);
int t=x;x=y;
y=t-(a/b)*y;
return ;
}
ll n,m,a[maxn],b[maxn],c[maxn],f[maxn];
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read(),b[i]=read();
c[i]=a[i]-b[i];f[0]+=b[i];
}
sort(c+1,c+n+1,greater<int>());
for(int i=1;i<=n;i++)f[i]=f[i-1]+c[i];
m=read();
while(m--){
ll a,b,x,y,d;
a=read(),b=read();
exgcd(a,b,d,x,y);
if(n%d){puts("-1");continue;}
ll modx=b/d,mody=a/d;
x=x*n/d;x=(x%modx+modx)%modx; //最小的x
y=y*n/d;y=(y%mody+mody)%mody; //最小的y
if(x*a>n || y*b>n){puts("-1");continue;}
ll l=0,r=((n-y*b)/a-x)/modx;
while(r>l){
ll m1=l+(r-l)/3;
ll m2=r-(r-l)/3;
if(f[(x+m1*modx)*a]<=f[(x+m2*modx)*a])l=m1+1;
else r=m2-1;
}
cout<<f[(x+l*modx)*a]<<endl;
}
return 0;
}
2.E - Chinese Restaurant (Three-Star Version)
题目:有n个人坐成一桌,定义一个人的沮丧程度为他喜欢的菜距离他的最小值,请随意逆时针旋转桌子,最小化所有人的沮丧程度。
题解:
不难发现旋转过程中,对于特定i,(令a[i]表示旋转后i菜的位置),a[i]到i的距离大小变化分为两部分,一部分增加,一部分减少
假设对于i,旋转v次,a[i]=i,那么对于i,距离和旋转次数的关系分两类
1.\(v<\frac{n}{2}\)
2.\(\frac{n}{2}\leq v\)
那么我们将\(i=1~n\)每个点的关系都加在一起,然后得到整个表达式
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,a[maxn];
ll k[maxn],b[maxn];
void add(int l,int r,int kk,int bb){
if(l>r)return ;
k[l]+=kk;k[r+1]-=kk;
b[l]+=bb;b[r+1]-=bb;
return ;
}
void add(int v){
if(v<n/2){
add(0,v-1,-1,v);
add(v,v+n/2,1,-v);
add(v+n/2+1,n-1,-1,n+v);
}
else {
add(0,v-n/2-1,1,n-v);
add(v-n/2,v-1,-1,v);
add(v,n-1,1,-v);
}
return ;
}
int main(){
n=read();
for(int i=0;i<n;i++)a[i]=read();
for(int i=0;i<n;i++){
int v=(a[i]-i+n)%n;add(v);
}
for(int i=1;i<n;i++)k[i]+=k[i-1],b[i]+=b[i-1];
ll ans=k[0]*0+b[0];
for(ll i=1;i<n;i++)ans=min(ans,k[i]*i+b[i]);
cout<<ans;
return 0;
}
3.D1. Zero-One (Easy Version)
首先知道当不同位置的个数为奇数时,输出-1
easy版本\(y\leq x\)
那么我们考虑这个性质,能用y就用y
若2个位置相邻,可以用2次y或者1次x
所以因为不同位置的个数为偶数,那么我们全用y就可以ans=cnt/2y
但要考虑特殊情况,个数=2,且相邻,答案=min(2y,x)
hard版本,我们只用考虑\(x<y\)
那么我们记忆化搜索
设dp[l][r]表示将[l,r]匹配的最小代价
那么我们只用从[l+1,r-1],[l+2,r],[l,r-2]三种情况转移
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=1e7+101;
const int MOD=998244353;
const ll inf=3000000000000+10101;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
vector<ll>pos;
ll dp[5005][5005],n,x,y;
ll get(ll l,ll r){
if(r-1==l)return x;
return min(y,x*(r-l));
}
ll dfs(int l,int r){
if(l>=r)return 0;
if(dp[l][r]!=-1)return dp[l][r];
ll ans=1e18;
ans=min(ans,dfs(l+1,r-1)+get(pos[l],pos[r]));
ans=min(ans,dfs(l,r-2)+get(pos[r-1],pos[r]));
ans=min(ans,dfs(l+2,r)+get(pos[l],pos[l+1]));
return dp[l][r]=ans;
}
void solve(){
n=read(),x=read(),y=read();
ll cnt1=0;pos.clear();
string s1,s2;cin>>s1>>s2;
for(int i=0;i<n;i++)if(s1[i]!=s2[i])cnt1++,pos.pb(i);
for(int i=0;i<=cnt1;i++)for(int j=0;j<=cnt1;j++)dp[i][j]=-1;
if(cnt1%2!=0){puts("-1");return ;}
if(cnt1==2 && pos[1]-pos[0]==1){cout<<min(2*y,x*(pos[1]-pos[0]))<<endl;return ;}
else if(x>=y){cout<<(cnt1)/2*y<<endl;return ;}
cout<<dfs(0,cnt1-1)<<endl;
return ;
}
int main(){
int t=read();
while(t--)solve();
return 0;
}
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=1e7+101;
const int MOD=998244353;
const ll inf=3000000000000+10101;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll a[130][130];
int get(ll t,ll x,ll y){
memset(a,0,sizeof(a));
a[0][0]=max(t-(x+y)+1,0ll);
for(int i=0;i<=x;i++){
for(int j=0;j<=y;j++){
a[i+1][j]+=a[i][j]/2;
a[i][j+1]+=a[i][j]-a[i][j]/2;
}
}
return a[x][y];
}
void solve(){
ll t=read(),x=read(),y=read();
if(get(t,x,y)-get(t-1,x,y))YES;
NO;
return ;
}
int main(){
int t=read();
while(t--)solve();
return 0;
}
5.D - Stones
题意:有n个石头,2玩家互相从中选\(a_i\)个石头,问先手最大拿多少个
题解:
贪心取能取到的最大是不行的
比如n=15,k=3,a[1]=1,a[2]=5,a[3]=6
贪心先手只能拿8个,而加入先手最开始拿5个,那么先手最后可以拿10个
我们设\(dp[i]\)表示当前有i个石头,先手能拿的最大值
\(dp[i]=max( a[j]+(i-a[j])-dp[i-a[j]] )\)
转移含义就是先手从i个石头中拿到的最大值=a[j]加上 (i-a[j])减去从后手从i-a[j]中拿到的最大值
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("-1");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int dp[10001];
int main(){
int n=read(),k=read();
vector<int>a(k+1);
for(int i=1;i<=k;i++)a[i]=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=k && i-a[j]>=0;j++){
dp[n]=max(dp[n],a[j]+(i-a[j])-dp[i-a[j]]);
}
}
cout<<dp[n];
return 0;
}
6.D. Meta-set
题意:一种卡牌由k个维度,我们把它当作一个向量。向量上每一个位置的值是0/1/2之一,对于三张卡牌的组合,每一个维度上都相同或者都不同,那么这个组合叫做一个set。对于五张牌的组合,内部存在2及以上的set的的话,这个五张牌的组合叫做Meta-set。给定n张牌,求有多少个Meta-set。
题解:
我们先看几组set,[0, 1, 2] [0, 1, 2], [0, 1, 2],[1, 2, 0] [0, 1, 0] [2, 0, 0]这样的组合在每一个维度上满足条件。
那么我们如何计数呢?首先因为每个向量都不同(题目说的),5张牌个中要有2个set,也就是说有一个牌被使用了两次,我们可以以这个必然被用两次的牌为出发点,看看其他的哪两张牌和他组合可以组成一个set。
快速判断2张牌的和 和 枚举的卡牌能够成为一个set:
我们这里需要用到一个3进制的状态压缩。因为状态只有0/1/2,k也非常小,因此我们把一个向量直接压缩成一个三进制的数。
比如卡牌[0, 1, 2]和[2, 1, 2],那么和他匹配的卡是[1, 1, 2],这个如何快速算呢?我们发现对应维度上每个数和 % 3必须等于0。
因此我们可以先处理出前两张牌的和,还差哪个数可以组成一个set,然后用map存下数统计出数量。然后线性枚举用2次的牌,因为我们需要2个set,所以在mp[state]这么多情况中需要提取2个,因此贡献为C[cnt, 2]。然后累计答案即可。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
struct Node { // 向量 三进制状压
ll c[30];
Node operator + (const Node &t) {
Node res;
for(int i = 0 ; i < 21 ; i ++ )
res.c[i] = (6 - c[i] - t.c[i]) % 3;
return res;
}
ll calc() {
ll res = 0;
for(int i = 0 ; i < 21 ; i ++ ) {
res = res * 3ll + c[i];
}
return res;
}
}a[2001];
void solve(){
int n=read(),k=read();
for(int i=1;i<=n;i++)for(int j=1;j<=k;j++)a[i].c[j]=read();
map<ll,int>mm;
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++){
Node res=a[i]+a[j];
mm[res.calc()]++;
}
ll ans=0;
for(int i=1;i<=n;i++){
ll cnt=mm[a[i].calc()];
ans+=cnt*(cnt-1)/2;
}
cout<<ans<<endl;
return ;
}
int main(){
int t=1;
while(t--)solve();
return 0;
}
7.C - Path and Subsequence
题意:判断一张n个点的图,问所有从1 到 n的路径中,是否每条路径的点权对应在A中的数所构成的序列包含B序列
题解:
设\(dp_u\)表示从1 到 u 已经匹配了B序列的前\(dp_u\)位,当然是越小越好
那么考虑从\(u到v\)的转移
\(dp_v=min(dp_v, dp_u+ ( dp_u <k \:\:AND \:\: a[v]==b[dp_u+1] ) )\)
可以用Dij最短路来求\(dp_n\)
但是发现每次转移的贡献是0/1,可以用01BFS更高效的来求
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("Yes");return;}
#define NO {puts("No");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,k,a[maxn],b[maxn],dp[maxn],vis[maxn];
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int main(){
n=read();m=read();k=read();
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=k;i++)b[i]=read();
deque<int>q;dp[1]=(a[1]==b[1]);
q.push_front(1);
while(!q.empty()){
auto u=q.front();q.pop_front();
if(vis[u])continue;vis[u]=1;
for(int i=head[u];i;i=nx[i]){
int v=to[i],now=(dp[u]<k && a[v]==b[dp[u]+1]);
if(dp[v]>dp[u]+now){
dp[v]=now+dp[u];
if(!now)q.push_front(v);
else q.push_back(v);
}
}
}
if(dp[n]==k)puts("Yes");
else puts("No");
return 0;
}
8.B - Make Divisible
题意:求最小的\(X+Y\),使得\(B+Y\)是\(A+X\)的倍数
题解
为什么要\(X_{min}=\lfloor \frac{B-1}{k} \rfloor +1 -A\)?
等价于:
若\(B\%k==0\),\(X_{min}=\frac{B}{k} -A\)
否则,\(X_{min}=\frac{B}{k}+1 -A\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
void solve(){
ll a=read(),b=read();
ll ans=inf,up=b-1;
if(b%a==0){puts("0");return ;}
for(ll l=1,r;l<=up;l=r+1){
//k在[l,r], Q=(b-1)/k相同
r=up/(up/l);
ans=min(ans,(l+1)*max(up/l+1-a,0ll)+l*a-b);
}
cout<<ans<<endl;
return;
}
int main(){
int t=read();while(t--)solve();
return 0;
}
9.B. Strange Permutations
x条禁选边,也就是确认x+1个点,那么剩下n-x-1个点的排列有\((n-x-1)!\)种方案
将n个点构成排列的方案数为\((n-x-1)!*(n-(x+1)+1)=(n-x)!\)
启发式合并用分治NTT就行
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=6e5+11;
const int MOD=998244353;
const int inf=2147483647-2;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll power(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%MOD;
y>>=1;x=x*x%MOD;
}
return ans;
}
ll fac[maxn],inv[maxn];
ll C(ll n,ll m){
if(n==m || m==0)return 1ll;
ll ans=fac[n]*inv[m]%MOD;
ans=ans*inv[n-m]%MOD;
return ans;
}
typedef vector<ll> Poly;
int rev[maxn];
void get(int bit){
for(int i=0;i<(1<<bit);i++)rev[i]=(rev[i>>1]>>1)|((1&i)<<(bit-1));
return ;
}
void ntt(ll *a,int n,int f){
get(log2(n));
for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1){
ll wn=power(3,(MOD-1)/(i<<1))%MOD;
if(f==-1)wn=power(wn,MOD-2);
for(int j=0;j<n;j+=i<<1){
ll w=1,x,y;
for(int k=0;k<i;k++,w=wn*w%MOD){
x=a[k+j];y=a[k+j+i]*w%MOD;
a[j+k]=(x+y)%MOD;a[j+k+i]=(x-y)%MOD;
}
}
}
if(f==1)return ;
int nv=power(n,MOD-2);
for(int i=0;i<n;i++)a[i]=a[i]*nv%MOD;
return ;
}
ll F1[maxn],F2[maxn];
Poly mul(Poly A,Poly B){ //求多项式A*B
int n=A.size(),m=B.size(),lens=n+m-1;
int bit=ceil(log2(lens));lens=(1<<bit);
for(int i=0;i<lens;i++)F1[i]=F2[i]=0;
for(int i=0;i<n;i++)F1[i]=A[i];
for(int i=0;i<m;i++)F2[i]=B[i];
ntt(F1,lens,1);ntt(F2,lens,1);
for(int i=0;i<lens;i++)F1[i]=F1[i]*F2[i]%MOD;
ntt(F1,lens,-1);
Poly ans;
for(int i=0;i<n+m-1;i++)ans.push_back(F1[i]);
return ans;
}
ll n,p[maxn],f[maxn],sz[maxn];
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
int tot;
Poly a[maxn];
Poly solve(int l,int r){
if(l==r)return a[l];
int mid=(l+r)>>1;
return mul(solve(l,mid),solve(mid+1,r));
}
int mm[maxn];
int main(){
n=read();
fac[0]=1;
for(ll i=1;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
inv[n]=power(fac[n],MOD-2);
for(ll i=n-1;i>0;i--)inv[i]=inv[i+1]*(i+1)%MOD;
for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;
for(int i=1;i<=n;i++){
p[i]=read();
int xx=find(i),yy=find(p[i]);
if(xx!=yy){
f[xx]=yy;
sz[yy]+=sz[xx];
}
}
for(int i=1;i<=n;i++){
int xx=find(i);
if(!mm[xx]){
mm[xx]=++tot;
for(ll j=0;j<sz[xx];j++)a[tot].pb(C(sz[xx],j));
a[tot].pb(0);
}
}
Poly ans=solve(1,tot);
ll an=0;
for(int i=0;i<=n;i++){
if(i&1)an+=(-1)*fac[n-i]*ans[i]%MOD;
else an+=fac[n-i]*ans[i]%MOD;
an%=MOD;
}
cout<<(an%MOD+MOD)%MOD;
return 0;
}
10.E - Notebook
我们考虑一个很暴力的做法就是用很多个栈来维护,但这样我们每次save或者load操作的时候都需要将栈重新赋值,时间复杂度是\(O(n)\),那么Q次操作我们复杂度肯定是会TLE的,所以我们得换一种数据结构来维护。
考虑链表,链表每个点记录自己的前驱
对于每页记录当前页的尾指针,通过尾指针就能遍历整页
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("Yes");return;}
#define NO {puts("No");return ;}
using namespace std;
const int maxn=7e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
vector<vector<int> >now(maxn);
unordered_map<int,int>pre,mm,val;
int tot;
int main(){
int q=read();
int end=0,tot=0;val[0]=-1;
while(q--){
char ch[10];scanf("%s",&ch);
int x;
if(ch[0]!='D')x=read();
if(ch[0]=='A'){
now[end].pb(++tot);
pre[tot]=end;
val[tot]=x;
end=tot;
}
if(ch[0]=='S')mm[x]=end;
if(ch[0]=='L')end=mm[x];
if(ch[0]=='D')end=pre[end];
printf("%d ",val[end]);
}
return 0;
}