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 ∑ai∗xi/∑bi∗xi的最值,其中 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
∑ai∗xi/∑bi∗xi>=mid
∑
(
a
i
−
b
i
∗
m
i
d
)
∗
x
i
>
=
0
\sum (a_i-b_i*mid)*x_i>=0
∑(ai−bi∗mid)∗xi>=0
那么想让这个式子成立,显然就 a i − b i ∗ m i d a_i-b_i*mid ai−bi∗mid大于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
∑ai∗xi/∑xi
也就是
b
i
=
0
b_i=0
bi=0的特殊情况,直接用0/1分数规划的通解求解即可,也就是考虑
∑
(
a
i
−
m
i
d
)
>
=
0
\sum (a_i-mid)>=0
∑(ai−mid)>=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,dpn−1)即可,这俩分别对应的就是最后一个元素选了和没选的,整个序列的答案。当然也可以类似打家劫舍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,2n−1]这些元素,每个都有个代价,选一些元素,可以异或出来 [ 1 , 2 n − 1 ] [1,2^n-1] [1,2n−1]所有元素,问最小代价
代价升序排序,然后逐一往线性基里插入即可,如果插入成功就把代价计入答案。
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;
}