好题选讲(1)

[bzoj4300]绝世好题

Description

给定一个长为$n$的数列$a$,要求选出一个$a$的子集$b$,使得$b$的大小最大,且满足$b_i\space \&\space b_{i-1} ≠ 0$。

$n\le 10^5$

Sol

我们可以轻松得出一个$\mathcal{O}(n^2)$的DP方程式,令$f_i$表示以$i$结尾的最大子集的数的个数,那么我们有

$$f_i = \max\{f_j + 1\space |\space a_i \space\&\space a_j ≠ 0\}$$

我们考虑两个数进行与运算时的运算规则为,当两个数某位置上的数均为$1$时,答案的该位才为$1$。

所以我们令$f_i$表示满足运算后第$i$位总为$1$的子集的最大长度,还是根据和上面差不多的思路枚举即可。

时间复杂度$\mathcal{O}(n\log n)$

Code

#include<bits/stdc++.h>
using namespace std;
int f[35], n, a[100005];
int main() {
    scanf("%d", &n); int ans = 0;
    for(int i = 1; i <= n; i++)  scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++) {
        int k = 0;
        for(int j = 0; j <= 30; j++)
            if(a[i] & (1 << j))  k = max(k, f[j] + 1);
        for(int j = 0; j <= 30; j++)
            if(a[i] & (1 << j))  f[j] = k;
        ans = max(ans, k);
    }
    cout << ans << endl;
}
View Code

[CF618F] Double Knapsack

Description

给定两个长为$n$的数组$a,b$,要求从$a,b$中个选出它们的一个子序列,使得两个子序列的总和相等。

$n\le 10^6, 0\le a_i,b_i\le 10^6$

Sol

不妨设$a$数组内所有数之和最大,那么我们可以记录$a,b$的前缀和$sa, sb$。

对于每个$i\in [0,n]$,我们枚举最小的$j\in [0,n]$且$sa_i-sb_j \in [0,n)$,这个可以用双指针来$\mathcal{O}(n)$实现。

当我们找到两个相同的$sa_i-sb_j$的值时,我们就可以取出它们之间的部分,那部分必定是相等的。

因为是$n+1$个数填入$n$个空内,所以根据鸽巢原理,必定至少有两个数为同一个值,所以一定有解。

时间复杂度$\mathcal{O}(n)$

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, a[1000005], b[1000005], sa[1000005], sb[1000005];
struct mp {
    int x, y;
}m[1000005];
signed main() {
    n = Read();
    for(int i = 1; i <= n; i++)
        a[i] = Read(), sa[i] = sa[i - 1] + a[i];
    for(int i = 1; i <= n; i++)
        b[i] = Read(), sb[i] = sb[i - 1] + b[i];
    for(int i = 0; i <= n; i++)  m[i] = (mp){-1, -1};
    bool rev = 0;
    if(sa[n] < sb[n])  swap(sa, sb), rev = 1;
    int i, v, pos = 0;
    for(i = 0; i <= n; i++) {
        while(sa[i] >= sb[pos + 1] && pos + 1 <= n)  ++pos;
        v = sa[i] - sb[pos];
        if(m[v].x != -1)  break;
        m[v].x = i, m[v].y = pos;
    }
    if(rev)  swap(i, pos), swap(m[v].x, m[v].y);
    printf("%lld\n", i - m[v].x);
    for(int j = m[v].x + 1; j <= i; j++)  printf("%lld ", j);
    puts("");
    printf("%lld\n", pos - m[v].y);
    for(int j = m[v].y + 1; j <= pos; j++)  printf("%lld ", j);
    return 0;
}
View Code

[bzoj2118]墨墨的等式

Description

 给定长度为$n$的数列$a$,求$[l,r]$内所有满足方程$\sum_{i=1}^n a_ix_i=b$的所有解非负的$b$的个数。

$n\le 12,a_i\le5\times10^5,l,r\le 10^{12}$

Sol

观察到如果一组解为$\{x_1,x_2,...,x_n\}$,那么必有一组解$\{x_1+1,x_2,...,x_n\}$

所以我们令数列内最小的非零数为$minn$,那么我们只需要得出剩下的数组合起来对$minn$取模的余数有多少种与在取得该余数时最小是多少。

那么我们可以想到最短路,将每个余数看做一个点,并连向其能够加一次到达的点,跑最短路即可。

时间复杂度$\mathcal{O}(n\times \min\{a_i\}+kn)$

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int first[6000005], nxt[6000005], to[6000005], w[6000005], tot = 0;
void Add(int x, int y, int z) {
    nxt[++tot] = first[x];
    first[x] = tot;
    to[tot] = y;
    w[tot] = z;
}
int n, l, r, len, a[55], minn = 1E9, dis[500005], inq[500005];
void spfa(int st) {
    for(int i = 0; i < minn; i++)  dis[i] = 1E18;
    queue<int> q;
    q.push(st);
    dis[st] = 0;
    inq[st] = 1;
    while(!q.empty()) {
        int u = q.front(); q.pop(); inq[u] = 0;
        for(int e = first[u]; e; e = nxt[e])
            if(dis[to[e]] > dis[u] + w[e]) {
                dis[to[e]] = dis[u] + w[e];
                if(!inq[to[e]])  q.push(to[e]), inq[to[e]] = 1;
            }
    }
}
int query(int x) {
    int res = 0;
    for(int i = 0; i < minn; i++)
        if(dis[i] <= x)  res += (x - dis[i]) / minn + 1;
    return res;
}
signed main() {
    n = Read(), l = Read(), r = Read();
    for(int i = 1; i <= n; i++) {
        a[i] = Read();
        if(a[i])  a[++len] = a[i], minn = min(minn, a[i]);
    }
    sort(a + 1, a + len + 1);
    n = len;
    for(int j = 0; j < minn; j++)
        for(int i = 1; i <= n; i++)
            if(a[i] != minn)
                Add(j, (j + a[i]) % minn, a[i]);
    spfa(0);
    printf("%lld\n", query(r) - query(l - 1));
    return 0;
}
View Code

[bzoj3687]简单题

Description

给定长为$n$的数列$a$,求其所有子集算术和的异或和。

$n\le 1000,\sum a_i\le 2000000$

Sol

观察数据范围,容易想到应用bitset求子集类问题,如果一个值在bitset中的值为$1$,那么它对答案就有贡献。

初始化$b_0=1$

当插入一个数时,我们直接左移原bitset并与原bitset异或,最终查询扫一遍即可。

时间复杂度$\mathcal{O}(\frac{n^2}{32})$

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
bitset<2000010> a;
signed main() {
    int n, x;
    scanf("%d", &n);
    a[0] = 1;
    for(int i = 1; i <= n; i++) {
        scanf("%d", &x);
        a ^= a << x;
    }
    int ans = 0;
    for(int i = 1; i <= 2000000; i++) {
        if(a[i])  ans ^= i;
    }
    printf("%d\n", ans);
    return 0;
}
View Code

[bzoj1076]奖励关

Description

Sol

由于是期望问题,我们考虑逆推。

设$f_{i,j}$表示前$i-1$轮一共取到的宝物集合为$j$,从第$i$轮到第$k$轮的期望得分,答案即为$f_{1,0}$

那么我们可以枚举第$i$轮取哪些宝物,如果可以取,那么就分取与不取两种情况讨论,方程式为:

$$f_{i,j}+=\max\{f_{i+1,j},f_{i+1,j+2^l}\}$$

如果不能取,方程式就为:

$$f_{i,j}+=f_{i+1,j}$$

因为我们一共讨论了$n$种情况,那么期望值就要除以$n$。

时间复杂度$\mathcal{O}(kn2^n)$

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, k, qz[55], val[55];
double f[105][50005];
signed main() {
    k = Read(), n = Read();
    for(int i = 1; i <= n; i++) {
        val[i] = Read();
        int x;
        while(x = Read())  qz[i] += 1 << (x - 1);
    }
    for(int i = k; i >= 1; i--) {
        for(int j = 0; j < (1 << n); j++) {
            for(int l = 1; l <= n; l++) {
                if((j & qz[l]) == qz[l])
                    f[i][j] += max(f[i + 1][j], f[i + 1][j | (1 << (l - 1))] + val[l]);
                else  f[i][j] += f[i + 1][j];
            }
            f[i][j] /= n;
        }
    }
    printf("%.6lf\n", f[1][0]);
    return 0;
}
View Code

 

posted @ 2020-09-14 13:37  verjun  阅读(154)  评论(1编辑  收藏  举报