A. Road To Zero
题意: 给定 x 和 y ,a 和 b ,有两种操作
①:花费 a ,选择 x 或 y 加一或者减一;
②:花费 b ,同时给 x 和 y 加一或者减一;
问最少需要花费多少可以使 x y 都为 0;
分析: 首先 x 和 y 之间的差值肯定是必须由 ① 操作补平的,然后考虑 x=y 的时候,是连续两次 ① 操作把 x 和 y 的值向 0 靠近 一个单位还是一次 ② 操作,比较一下就可以了,所以是简单的贪心;
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t; cin>>t;
while(t--)
{
ll a,b,x,y; cin>>x>>y>>a>>b;
ll ans=abs(x-y)*a;
ans+=min(min(x,y)*b,min(x,y)*2*a);
cout<<ans<<endl;
}
}
B. Binary Period
题意: 给定一个仅由 0 和 1 组成的序列 t ,现需要你构造一个序列 s 满足一下条件
①:s 仅由 0 和 1 组成;
②:s 的长度不超过 t 的两倍,即 |s|<=2*|t|;
③:t 是 s 的子序列;
④:在满足条件①-③的情况下令 s 的循环节最小;
分析: 如果 t 仅包含 0 或 1,那么 t 就是满足上述四个条件的答案序列,显然它的最小循环节长度是 1;否则,只需要在 t 中找到相邻相同的位置然后中间插入不一样的,最后使序列变成 101010... 或者 010101.. 的形式就可以了,它的最小循环节是 2;
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t; cin>>t;
while(t--)
{
string s; cin>>s;
int n=s.length();
int z=0,o=0;
for(int i=0;i<n;i++) if(s[i]=='0') z++;else o++;
if(!z||!o) cout<<s<<endl;
else
{
cout<<s[0];
int pre=s[0]-'0';
for(int i=1;i<n;i++)
if((s[i]-'0')!=pre)
cout<<s[i],pre^=1;
else
cout<<(pre^1)<<s[i];
cout<<endl;
}
}
}
C. Yet Another Counting Problem
题意: 给定 a 和 b ,求区间 [l,r] 内有多少数满足
分析: 先把 x 的形式变一变
对于 lcm(a,b)*k
部分,不论先模 a 还是 b 最后都是 0 ,所以对结果有影响的部分是 k,这样范围就缩小到了 [1,lcm(a,b)]
,接下来遍历统计区间 [1,lcm(a,b)] 内的情况就可以了;
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int s[200*200+19];
ll gcd(ll a,ll b){
if(a==0) return b;
if(b%a==0) return a;
return gcd(b%a,a);
}
ll lcm(ll a,ll b){
return a/gcd(a,b)*b;
}
ll l[600],r[600];
int main()
{
ios::sync_with_stdio(false);
int t;cin>>t;
while(t--)
{
memset(s,0,sizeof(s));
ll a,b,q; cin>>a>>b>>q;
ll L=lcm(a,b),cnt=0;
for(ll i=1;i<=L;i++)
{
if(((i%a)%b)!=((i%b)%a)) s[i]=1,cnt++; //统计区间[1,lcm(a,b)]的情况
}
for(int i=2;i<=L;i++) s[i]+=s[i-1]; //做一下前缀和
for(int i=1;i<=q;i++) cin>>l[i]>>r[i],l[i]--;
for(int i=1;i<=q;i++)
{
ll A=r[i]/L*cnt+s[r[i]%L];
ll B=l[i]/L*cnt+s[l[i]%L];
cout<<A-B;
if(i==q) cout<<endl;
else cout<<' ';
}
}
}
D. Multiple Testcases
题意: 给定长度为 n 的 m 数组 ,然后给定长度为 k 的 c 数组,现在给 m 数组内的元素分组,要求组数尽量少的前提下满足每组的元素 ( (m_i<=k) )
①:大于 1 的元素不超过 (c_1) 个;
②:大于 2 的元素不超过 (c_2) 个;
...
③:大于 k 的元素不超过 (c_k) 个;
求具体分组方案,((n geq c_1 geq c_2 geq ... geq c_k geq 1))
分析: c 数组是非递增的,我们把 m 数组内的元素由大到小进行分组就可以了;
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2E5+10;
vector<int>ans[N];
int cnt=0;
int n,k,m[N],c[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>k;
for(int i=1,x;i<=n;i++) cin>>x,m[x]++;
for(int i=1;i<=k;i++) cin>>c[i];
int l,r=n,sum=0,H=1;
for(int i=k;i>=1;i--)
{
if(i!=k&&c[i]>c[i+1]) H=1; //c[i]>c[i+1],即前面的组还可以再分配元素,下标H初始化
while(m[i]--)
{
while(ans[H].size()==c[i]) H++;
ans[H].push_back(i);
}
cnt=max(cnt,H);
}
cout<<cnt<<'
';
for(int i=1;i<=cnt;i++)
{
int n=ans[i].size();
cout<<n;
for(auto s:ans[i]) cout<<' '<<s;
cout<<'
';
}
}
E. Placing Rooks
题意: 让你在一个 (n imes n) 的棋盘内放置 n 颗石子,最后的棋局需要满足
①:棋盘上所有的点都被石子的攻击范围覆盖 (一个石子的攻击范围是它所在位置的那一行与那一列);
②:有 k 对 石子互相攻击 (互相攻击的定义是两颗石子属同一行或同一列,且之间无其它石子);
输出满足上述两个条件的棋局的方案总数 (mol 998244353)
分析: 若要满足条件①,那么每一行(或者每一列)都至少得有一颗石子,即一行(或一列)一颗。而行列的情况是对称的,所以仅考虑每一行摆一颗石子的方案,最后对结果乘以2就可以了( k=0 除外,因为 k=0 等价于每列每行同时只摆一颗石子,所以不用乘以2)
然后,需要正好 k 对石子互相攻击,一列中如果有 m 颗石子,那么会有 m-1 对石子相互攻击,所以至多只能满足 n-1 对石子相互攻击(即所有石子放在同一列),那么显然 k>n-1 的情况无解;接下来,我们设 n 颗石子一共摆了 x 列(每列至少一颗),那么一共有 n-x 对石子互相攻击(每列是它的石子数-1),所以只需要把 n 颗石子都放在 n-k 列中就可以了。列数的方案一共有 C(n,n-k) 种,可是具体的摆放方案还是很复杂,这里就可以用容斥原理来解决:
首先,选择完确定的 n-k 列之后,没有任何限制条件,一共有 ((n-k)^n) 种摆放方案,然后减去至少有一列为空的方案,即 (C(n-k,1) imes (n-k-1)^n) ,再加上至少有两列为空的方案,即 (C(n-k,2) imes (n-k-2)^n) ...
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int N = 2E5+10;
ll qpow(ll a,ll n)
{
ll res=1;
while(n)
{
if(n&1) res=res*a%MOD;
a=a*a%MOD;
n>>=1;
}
return res;
}
ll fac[N];
ll C_mod(ll n,ll m)
{
ll fm,fz;
fm=fac[m]*fac[n-m]%MOD;
fz=fac[n];
return fz*qpow(fm,MOD-2)%MOD;
}
//Lucas定理,组合数取模
ll Lucas(ll n,ll m)
{
if(!m) return 1;
return C_mod(n%MOD,m%MOD)*Lucas(n/MOD,m/MOD)%MOD;
}
int main()
{
fac[0]=fac[1]=1;
for(int i=2;i<N;i++) fac[i]=fac[i-1]*i%MOD; //预处理阶乘
ll n,k; cin>>n>>k;
if(k>=n) cout<<"0",exit(0); //无解的情况
ll m=n-k;
ll ANS=0;
for(int i=0;i<m;i++)
{
ll res=qpow(m-i,n)*Lucas(m,i)%MOD;
if(i%2) ANS=(ANS-res+MOD)%MOD;
else ANS=(ANS+res)%MOD;
}
ANS=(ANS*Lucas(n,m))%MOD;
if(k) ANS=ANS*2%MOD;
cout<<ANS;
}
F. Make It Ascending
题意: 给定一个长度为 n 的数组 a (n<=15),你可以进行若干次如下操作
- 选择 i 和 j ,a[j]+=a[i] 并且删去 a[i] ;(删去之后,i 之后的元素下标都往前移一位)
求最少操作次数,使得最后的数组 a 严格单调递增,并给出具体操作方案;
分析: 我们先不考虑删除操作,问题相当于将原数组 a 内的元素分成若干组,每一组累加所有的元素和放到其中一个元素原来的位置,其它位置无效,最后所有有效位置的数从左到右严格单调递增,那么我们从左到右(即从小到大)枚举最终的数组,n <= 15 ,所以可以用状压,设 dp[i][j][z]
表示枚举到第 i 组,前 i 组的状态集合 j,最右边(即最大的)的数放置的位置为 z 的情况下第 i 组的最小值,那么
- 第 i+1 组的状态肯定是从 (j^(1<<n)-1) 中枚举而来;
- 第 i+1 组的 z 值肯定是大于第 i 组的;
for(int i=0;i<=n;i++)
for(int j=0;j<(1<<n);j++)
for(int z=0;z<=n;z++)
dp[i][j][z]=INF; //初始化
dp[0][0][0]=0;
for(int i=1;i<=n;i++) //第 i 组
for(int j=0;j<(1<<n);j++) //前一组的状态
for(int z=0;z<n;z++) //前一组最右边的数所在的位置
{
if(dp[i-1][j][z]==INF) continue;
int m=j^((1<<n)-1); //当前组的集合全集
for(int k=m;k;k=(k-1)&m)
{
if(sum[k]<=dp[i-1][j][z]) continue;
if((k>>z)==0) continue; //第i+1组的z值肯定要大于第i组
int nz=z+__builtin_ctz(k>>z)+1;
if(dp[i][j|k][nz]>sum[k])
{
dp[i][j|k][nz]=sum[k];
p[i][j|k][nz]=P(j,z); //记录路径
}
}
}
完整代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef pair<int,int> P;
const int N = 15;
const int INF = 1E9+7;
int dp[N+1][1<<N][N+1];
P p[N+1][1<<N][N+1];
int n,a[N],sum[1<<N];
void work()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<(1<<n);i++)
{
sum[i]=0;
for(int j=0;j<n;j++) if((1<<j)&i) sum[i]+=a[j];
}
for(int i=0;i<=n;i++)
for(int j=0;j<(1<<n);j++)
for(int z=0;z<=n;z++)
dp[i][j][z]=INF;
dp[0][0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<(1<<n);j++)
for(int z=0;z<n;z++)
{
if(dp[i-1][j][z]==INF) continue;
int m=j^((1<<n)-1);
for(int k=m;k;k=(k-1)&m)
{
if(sum[k]<=dp[i-1][j][z]) continue;
if((k>>z)==0) continue;
int nz=z+__builtin_ctz(k>>z)+1;
if(dp[i][j|k][nz]>sum[k])
{
dp[i][j|k][nz]=sum[k];
p[i][j|k][nz]=P(j,z);
}
}
}
//找到最优解
int a=-1,c=-1;
for(int i=n;i>=0;i--)
{
for(int z=n;z>=0;z--)
if(dp[i][(1<<n)-1][z]<INF){
c=z; break;
}
if(c!=-1){
a=i;break;
}
}
vector<P>ans;
int b=(1<<n)-1;
for(int i=a;i>0;i--)
{
int nb=p[i][b][c].fi;
int nc=p[i][b][c].se;
int m = nb^b;
for(int j=0;j<n;j++)
if(m&(1<<j)&&j!=c-1)
ans.push_back(P(j,c-1));
b=nb,c=nc;
}
cout<<(int)ans.size()<<'
';
for(int i=0;i<ans.size();i++)
{
int x=ans[i].fi;
int y=ans[i].se;
for(int j=0;j<i;j++) if(ans[j].fi<ans[i].fi) x--;
for(int j=0;j<i;j++) if(ans[j].fi<ans[i].se) y--;
cout<<x+1<<' '<<y+1<<'
';
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin>>T;
while(T--) work();
}