非常棒的二分和DP

日常刷题2025-3-15

F - K-th Largest Triplet

满青色

https://atcoder.jp/contests/abc391/tasks/abc391_f

思路:二分答案

一道顶级的二分答案加剪枝优化的题目

清北信息学有题解

代码

#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

蓝色

https://atcoder.jp/contests/abc390/tasks/abc390_d

思路:暴搜

读完题目先看数据范围,发现 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

青色

https://atcoder.jp/contests/abc390/tasks/abc390_e

思路

题目一读,范围一看,锁定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 普及组] 数字游戏

绿色

https://www.luogu.com.cn/problem/P1043

思路: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;
}
posted @ 2025-03-15 11:06  califeee  阅读(8)  评论(0)    收藏  举报