ABC326 0/1分数规划 线性基

D

dfs

2n个人,每个人和另一个人配对有一个贡献,最后配对n组的贡献就是每一对的贡献异或起来,问贡献最值?

n = 8 n=8 n=8直接dfs枚举所有配对。需要一个数组记录每个人是否配对,如果当前层的人已经配对了直接进入下一层;如果没配对,枚举另一个没配对的人和当前人配对,并标记已配对,然后进入下一层。dfs返回时需要撤销标记

void solve(){
	int n;
	cin>>n;
	vvi a(2*n+1,vi(2*n+1));
	rep(i,1,n*2-1){
		rep(j,i+1,n*2){
			cin>>a[i][j];
		}
	}
	vi vis(2*n+1);
	int ans=0;
	auto &&dfs=[&](auto &&dfs,int i,int sum)->void{
		if(i==2*n+1){
			ans=max(ans,sum);
			return;
		}
		if(!vis[i]){
			vis[i]=1;
			rep(j,1,2*n){
				if(vis[j])continue;
				int x=i,y=j;
				if(x>y)swap(x,y);
				vis[j]=1;
				dfs(dfs,i+1,sum^a[x][y]);
				vis[j]=0;
			}
			vis[i]=0;
		}
		else{
			dfs(dfs,i+1,sum);
		}
	};
	dfs(dfs,1,0);
	cout<<ans;
}

E

01分数规划 二分 dp

给一个序列,选一些元素,不能连续两个不选。问选中元素平均值和中位数的最大值

求平均数最值其实是0/1分数规划的特殊情况,先来看01分数规划

∑ a i ∗ x i / ∑ b i ∗ x i \sum a_i*x_i/\sum b_i *x_i aixi/bixi的最值,其中 x i x_i xi取值为 0 / 1 0/1 0/1

可以二分,二分值为 m i d mid mid,那么有

∑ a i ∗ x i / ∑ b i ∗ x i > = m i d \sum a_i*x_i/\sum b_i *x_i>=mid aixi/bixi>=mid
∑ ( a i − b i ∗ m i d ) ∗ x i > = 0 \sum (a_i-b_i*mid)*x_i>=0 (aibimid)xi>=0

那么想让这个式子成立,显然就 a i − b i ∗ m i d a_i-b_i*mid aibimid大于0,就 x i = 1 x_i=1 xi=1,否则 x i = 0 x_i=0 xi=0,可以看到这是有单调性的,所以二分是对的

回到本题,平均数的定义是
∑ a i ∗ x i / ∑ x i \sum a_i*x_i/\sum x_i aixi/xi
也就是 b i = 0 b_i=0 bi=0的特殊情况,直接用0/1分数规划的通解求解即可,也就是考虑 ∑ ( a i − m i d ) > = 0 \sum (a_i-mid)>=0 (aimid)>=0是否成立

原始的01分数规划,选元素是没有任何约束,每个元素都可以选或不选,本题还要求不能连续两个不选,也就是在选或不选的基础上来了个线性约束,考虑线性dp

d p i 表示考虑前 i 个,第 i 个选了的最大值 dp_i表示考虑前i个,第i个选了的最大值 dpi表示考虑前i个,第i个选了的最大值

我们必须考虑整个序列,并且最后一个元素可选可不选,因此看 max ⁡ ( d p n , d p n − 1 ) \max (dp_n,dp_{n-1}) max(dpn,dpn1)即可,这俩分别对应的就是最后一个元素选了和没选的,整个序列的答案。当然也可以类似打家劫舍dp,对每个位置设两个状态表示选没选。

对于中位数,其实就是二分第k大,把大于等于mid的元素设成1,其他元素设成-1,跑上述dp,看结果是否大于0即可。

void solve(){
	int n;
	cin>>n;
	vi a(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	db l=0,r=1e9;
	auto check=[&](db x)->bool{
		vector<db>dp(n+1);
		dp[1]=a[1]-x;
		rep(i,2,n){
			dp[i]=max(dp[i-1],dp[i-2])+a[i]-x;
		}	
		return max(dp[n],dp[n-1])>=0;
	};
	while(r-l>1e-6){
		db m=(l+r)/2;
		if(check(m))l=m;
		else r=m;
	}
	printf("%.6lf\n",l);
	int L=0,R=1e9;
	auto check1=[&](int x)->bool{
		vi dp(n+1);
		dp[1]=a[1]>=x?1:-1;
		
		rep(i,2,n){
			dp[i]=max(dp[i-1],dp[i-2])+(a[i]>=x?1:-1);
		}	
		
		return max(dp[n],dp[n-1])>0;
	};
	while(L<=R){
		int m=L+R>>1;
		if(check1(m))L=m+1;
		else R=m-1;
	}
	printf("%lld",R);
}

F

线性基,贪心

[ 1 , 2 n − 1 ] [1,2^n-1] [1,2n1]这些元素,每个都有个代价,选一些元素,可以异或出来 [ 1 , 2 n − 1 ] [1,2^n-1] [1,2n1]所有元素,问最小代价

代价升序排序,然后逐一往线性基里插入即可,如果插入成功就把代价计入答案。

class LinearBasis {
private:
    static const int MN = 20;
    long long a[MN + 1], tmp[MN + 1];
    int sz;
    bool flag;

public:
    LinearBasis() : sz(0), flag(false) {
        memset(a, 0, sizeof(a));
        memset(tmp, 0, sizeof(tmp));
    }

    bool insert(long long x) {
        for (int i = MN; i >= 0; i--) {
            if (x & (1LL << i)) {
                if (!a[i]) {
                    a[i] = x;
                    sz++;
                    return 1;
                }
                x ^= a[i];
            }
        }
        flag = true;
        return 0;
    }

    bool check(long long x) {
        for (int i = MN; i >= 0; i--) {
            if (x & (1LL << i)) {
                if (!a[i]) return false;
                x ^= a[i];
            }
        }
        return true;
    }

    long long queryMax(long long res = 0) {
        for (int i = MN; i >= 0; i--) {
            res = max(res, res ^ a[i]);
        }
        return res;
    }

    long long queryMin() {
        if (flag) return 0;
        for (int i = 0; i <= MN; i++) {
            if (a[i]) return a[i];
        }
        return 0;
    }

    long long queryKth(long long k) {
        long long res = 0;
        int cnt = 0;
        
        k -= flag;
        if (!k) return 0;

        for (int i = 0; i <= MN; i++) {
            for (int j = i - 1; j >= 0; j--) {
                if (a[i] & (1LL << j)) {
                    a[i] ^= a[j];
                }
            }
            if (a[i]) tmp[cnt++] = a[i];
        }

        if (k >= (1LL << cnt)) return -1;

        for (int i = 0; i < cnt; i++) {
            if (k & (1LL << i)) {
                res ^= tmp[i];
            }
        }
        return res;
    }
    
    int getsz(){
		return sz;
	}
}lb;
void solve(){
	int n;
	cin>>n;
	vvi a;
	rep(i,1,(1<<n)-1){
		int x;
		cin>>x;
		a.push_back({x,i});
	}	
	sort(a.begin(),a.end());
	
	int ans=0;
	for(auto &t:a){
		int x=t[1],cost=t[0];
		if(lb.insert(x)){
			ans+=cost;
		}
		if(lb.getsz()==n){
			break;
		}
	}
	cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值