非常棒的二分和DP
日常刷题2025-3-15
F - K-th Largest Triplet
满青色
思路:二分答案
一道顶级的二分答案加剪枝优化的题目
清北信息学有题解
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
int a[MAXN],b[MAXN],c[MAXN],n,k;
long long f(int i,int j,int k){
return 1ll*a[i]*b[j] + 1ll*b[j]*c[k] + 1ll*c[k]*a[i];
}
bool chk(long long mid){
// >=mid的数量是否 >=K
int cnt = 0;
for(int i = 1;i <= n;++i){
if(f(i,1,1) < mid) break;
for(int j = 1;j <= n;++j){
if(f(i,j,1) < mid) break;
for(int k = 1;k <= n;++k){
if(f(i,j,k) < mid) break;
++cnt;
if(cnt == ::k) return true;
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i) scanf("%d",a+i);
for(int i = 1;i <= n;++i) scanf("%d",b+i);
for(int i = 1;i <= n;++i) scanf("%d",c+i);
sort(a+1,a+n+1,[](int x,int y) -> bool {return x > y;});
sort(b+1,b+n+1,[](int x,int y) -> bool {return x > y;});
sort(c+1,c+n+1,[](int x,int y) -> bool {return x > y;});
long long l = 0, r = 3e18,ans = -1;
// printf("%d\n",chk(34));
// exit(0);
while(l <= r){
long long mid = (l + r) >> 1;
if(chk(mid)) ans = mid, l = mid+1;
else r = mid-1;
}
printf("%lld\n",ans);
return 0;
}
船长
铜牌
2025钉耙编程1002
思路:DP
把本题的逻辑模拟出来就是一个树形结构,比较容易想到DP,但是如果我们把所有n个节点定义出来思考他们的转移的话,会有一个大问题就是——足足有\(10^9\)那么大。但是注意到 k 的范围比较小,很明显出题人是要我们利用 k 的范围比较小来设计代码。那么如何利用到 k 呢?有一个方法使我们只去思考对我们有威胁的人的转移情况,其他人都是无关紧要的。
设 \(f_i\) 表示淘汰赛进行到节点 处剩下来的胜者是能对最终赢家造成威胁的人的概率。一轮一轮的向上模拟就行,顺便统计答案。
代码
#include <bits/stdc++.h>
typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
using i64 = long long;
const int mod=998244353,inv2=(mod+1)/2;
int Power(int a,int k){
int res=1;
for (;k;k>>=1,a=1LL*a*a%mod)
if (k&1) res=1LL*res*a%mod;
return res;
}
int inv(int a){
return Power(a, mod-2);
}
void solve(){
int n, k, win, ans = 1;
std::cin>>n>>k>>win;
win--;
std::vector<int> p(k), f(k,1);
for(int i=0;i<k;i++){
std::cin>>p[i];
p[i]--;
}
std::sort(p.begin(), p.end());
while(!p.empty()){
std::vector<int> q, g;
win>>=1;
for(int i=0;i<p.size(); ){
if((p[i]>>1)==win){
ans = 1ll * ans * (1 - f[i] + mod) % mod;
i++;
}else{
q.push_back(p[i]>>1);
if(i+1<(int)p.size()&&(p[i]>>1)==(p[i+1]>>1)){
g.push_back((1ll*inv2*(f[i]+f[i+1]))%mod);
i+=2;
}else if((p[i]^1)<n){
g.push_back((1ll*f[i]*inv2)%mod);
i++;
}else{
g.push_back(f[i]);
i++;
}
}
}
f = g;
p = q;
n = (n+1)>>1;
}
std::cout<<ans<<'\n';
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
int t = 1, i;
std::cin >> t;
for (i = 0; i < t; i++){
solve();
}
return 0;
}
D - Stone XOR
蓝色
思路:暴搜
读完题目先看数据范围,发现 n 的数据范围非常小,所以肯定是一个很暴力的做法。做XOR的题很容易让我们想到按位考虑,但是仔细思考一下会发现这道题完全没法按位考虑,因为加法带来的改变不是按位的,我们需要考虑非常多位,完全无法入手。
再次分析一下题目的性质。题目的意思其实等价于将现有的袋子分组,然后统计答案。我们考虑可不可以暴力求出所有分组。事实是可以的。
数学支持是bell数,可以去了解一下。
代码
#include <bits/stdc++.h>
using u64 = unsigned long long;
using i64 = long long;
typedef std::pair<int, int> pii;
const int mod = 998244353;
const long long LINF = 1e18;
const int N = 13;
int n;
std::vector<i64> a(N), b(N);
std::unordered_set<i64> st;
void dfs(int cur, int cnt){
if(cur>n){
i64 res=0;
for(int i=1;i<=cnt;i++) res^=b[i];
st.insert(res);
return;
}
//新开一组
b[cnt+1]=a[cur];
dfs(cur+1, cnt+1);
for(int i=1;i<=cnt;i++) {
b[i]+=a[cur];
dfs(cur+1, cnt);
b[i]-=a[cur];
}
}
void solve(){
std::cin>>n;
for(int i=1;i<=n;i++){
std::cin>>a[i];
}
dfs(1, 0);
std::cout<<st.size()<<'\n';
}
signed main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15);
int t = 1, i;
for (i = 0; i < t; i++){
solve();
}
return 0;
}
E - Vitamin Balance
青色
思路
题目一读,范围一看,锁定DP。直接掏出DP三板斧,状态,转移,初始化。
DP还得练。看到最小最大值一定要想到二分啊。
状态定义:
\(f[i][j]:\)在总卡路里少于j的情况下,枚举到了第i个食物,能得到的三种维生素的最小最大值*
如果只维护最小值,那么我们根本不知道这个最小值是哪个维生素,也就无法继续转移。想要转移就只能把
三种维生素在递推过程中的值都维护起来,但是这意味着我们需要新开三层数组。对空间来说这肯定是不可能
的。由于状态 i 只取决于状态 i-1,所以我们似乎只需要维护三个变量即可,在递推过程中维护他们的值
以上思路是完全错误的
正解:背包+二分
清北信息学堂有讲。没想到二分简直了。
代码
#include <bits/stdc++.h>
using u64 = unsigned long long;
using i64 = long long;
typedef std::pair<int, int> pii;
const int mod = 998244353;
const long long LINF = 1e18;
int n, x;
const int N = 5005;
int a[3][N], c[3][N], f[3][N][N];
int cnt[3];
bool check(int m){
int res=0;
for(int k=0;k<3;k++){
bool ok=false;
for(int i=0;i<=x;i++){
if(f[k][cnt[k]][i]>=m){
res+=i;
ok=true;
break;
}
}
if(!ok) return false;
}
return res<=x;
}
void solve(){
std::cin>>n>>x;
for(int i=0;i<n;i++){
int v, A, C; std::cin>>v>>A>>C;
v--;
cnt[v]++;
a[v][cnt[v]]=A;
c[v][cnt[v]]=C;
}
for(int k=0;k<3;k++){
for(int i=1;i<=cnt[k];i++){
for(int j=0;j<=x;j++){
f[k][i][j]=f[k][i-1][j];
}
for(int j=c[k][i];j<=x;j++){
f[k][i][j]=std::max(f[k][i][j], f[k][i-1][j-c[k][i]]+a[k][i]);
}
}
}
int l=0, r=1e9;
while (l<r){
int mid = (l+r+1)>>1;
if(check(mid)) l = mid;
else r = mid-1;
}
std::cout<<l<<'\n';
}
signed main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15);
int t = 1, i;
for (i = 0; i < t; i++){
solve();
}
return 0;
}
P1043 [NOIP 2003 普及组] 数字游戏
绿色
思路:DP处理分组
题目描述了一个环,所以第一步就是先断链。
第二部就是经典的DP处理数组分组问题,本题实际上就是把数组中的元素分成m组,让你计算答案。
\(f[i][j]\):前 i 个数分成 j 组
经典考虑方式是,枚举分割点 k 。则\(f[k][j-1]->f[i][j]\)
代码
#include <bits/stdc++.h>
using u64 = unsigned long long;
using i64 = long long;
typedef std::pair<int, int> pii;
const int mod = 998244353;
const long long LINF = 1e18;
const int MAXN = 55, MAXM = 55;
int n, m;
int c[MAXN*2], a[MAXN], f1[MAXN][MAXM], f2[MAXN][MAXM];
int sum[MAXN];
int ans1 = 0, ans2 = 0x7fffffff;
int mob(int x){
return ((x%10)+10)%10;
}
void solve(){
std::cin>>n>>m;
for(int i=1;i<=n;i++){
std::cin>>c[i];
c[i+n]=c[i];
}
for(int s=1;s<=n;s++){
int t = 0;
for(int i=s;i<=s+n-1;i++){
a[++t]=c[i];
sum[t]=sum[t-1]+a[t];
}
memset(f1,0,sizeof(f1));
for(int i=1;i<=n;i++){
//分成初始化分成一段时
f1[i][1]=mob(sum[i]);
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m&&j<=i;j++){
for(int k=j-1;k<=i-1;k++){
f1[i][j]=std::max(f1[i][j],f1[k][j-1]*mob(sum[i]-sum[k]));
}
}
}
ans1=std::max(ans1,f1[n][m]);
memset(f2, 0x3f3f3f3f, sizeof(f2));
for(int i=1;i<=n;i++){
f2[i][1]=mob(sum[i]);
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m&&j<=i;j++){
for(int k=j-1;k<=i-1;k++){
f2[i][j]=std::min(f2[i][j],f2[k][j-1]*mob(sum[i]-sum[k]));
}
}
}
ans2=std::min(ans2,f2[n][m]);
}
std::cout<<ans2<<'\n';
std::cout<<ans1<<'\n';
}
signed main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15);
int t = 1, i;
for (i = 0; i < t; i++){
solve();
}
return 0;
}