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(2
y,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;
}

4.E. Conveyor
题解

点击查看代码
#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;
}
posted @ 2022-09-10 13:48  I_N_V  阅读(22)  评论(0编辑  收藏  举报