[权值线段树][比赛失误][x] HDU 6609 Find the answer

题目链接

题意:

给了 $N$ 个数 $W_k$ , 对于每个 $1 \le i \le n$, 在 $i$ 以前不包括 $i$ 的所有 $W$ 里选取最少的一些 $W$ 将其变为 $0$ , 使得 从 $1$ 到 $i$ 的和不大于 $m$ 。要求对于每个 $i$ 输出最小的数目。

题解:

做的时候傻了,一直用set乱搞,搞来搞去总是tle,主要是用set维护真的会被卡。。用平衡树板子又有点大材小用,于是就米勒很久。

所以说遇到一个简单题很久没出的话,可能就得考虑全盘否定直接从来了。。。

如果一开始就没有被赵老师带着想set,而是直奔线段树去的话,就很好搞了,直接把问题转化为在前 i-1 个数里找出最少的数字使得之和大于 $pre[i]-m$ ,其中 $pre[i]$ 是前缀和。这样的话直接用权值线段树来做,每个节点存两个域,一个是数字的个数,一个是数字的和。。然后query的时候利用线段树天然的二分性质来搞就行了。。。

这题其实动机真的不难,就是因为不用数据结构的话每次要排序,然后权值线段树天生有排序的特性在里面。。。所以遇事不决还是先考虑线段树。。。不能一开始就想骚操作emmmmm
继续阅读“[权值线段树][比赛失误][x] HDU 6609 Find the answer”

[离线处理][scheduling][数论结论][x] HDU5869 Different GCD Subarray Query

题目链接

题意:

给定 $N$ 个数字, $Q$ 次询问,每次询问为查询下标在 $[l,r]$ 之间的所有数字中所有可能的子段的 $gcd$ 有多少种。$N,Q < 10^5 , a_i < 10^6$

题解:

首先一个重要的结论:以固定点为结尾的子段的 $gcd$ 种数不超过 $max(n,log(max(a_i)))$ 证明:每次往原有的序列里加入一个数字的话, $gcd$ 要么不变,要么至少变为原来的1/2,因此为指数级减小。

有了这个结论,我们可以试着来枚举一个端点考虑本题,不妨枚举每个段的右端点$r_j$ , 假设集合$S$表示以$r_{j-1}$ 为结尾的所有 $gcd$ ,那么要求出以$r_j$为结尾的所有 $gcd$ ,只需遍历S中的每个数字$d_k$,计算其与$a_j$ 的 $gcd$ ,放入新集合$S^{‘}$即可。这样的话我们可以得到一个空间为$O(nlogn)$的结果,表示以每个点为结尾的所有段的 $gcd$ 都是哪些数字。

现在来考虑查询。每次查询的话是要求出来$[l,r]$之间所有可能的 $gcd$ 。直接暴力不可取,由上面的结果可以试着考虑能否转化为前缀问题。可以发现,如果我们对所有的$[l,r]$离线处理,按$r$从小到大排序,并且按照这个顺序来调整已经遇到过的 $gcd$ 的信息的话,每次的查询只需要考虑有多少个 $gcd$ 出现的最右左端点大于等于l,这句话具体的意思是,一个 $gcd$ 可能出现过了很多次,不同的段都可以得到这个 $gcd$ ,但是我们只考虑这些段中左端点l_j最大的那个点$p_j$,这样的话如果$l<=p_j$那么可以确定$[l,r]$中一定有这个 $gcd$ (种类+1),这就就转化成了一个计数问题。 继续阅读“[离线处理][scheduling][数论结论][x] HDU5869 Different GCD Subarray Query”

火柴棒DP 【未完待续】

这种题主要是比较套路,在火柴棒的约束下搞事情

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
 
using namespace std;
#define pr(x) cout << #x << " = " << x << "  "
#define prln(x) cout << #x << " = " << x << endl
const int N = 1e5 + 10, INF = 0x3f3f3f3f, MOD = 1e9 + 7;
 
typedef long long LL;
 
int n, m;
LL dp[505][2][2][2]; //left, carry, place B, place C
int need[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
 
void add(LL& x, LL y) {
    x += y;
    if(x >= m) x -= m;
}
 
LL dfs(int n, bool carry, bool B, bool C) {
    LL& ret = dp[n][carry][B][C];
    if(~ret) return ret;
    if(n == 0) {
        if(!carry && !B && !C) return ret = 1;
        return ret = 0;
    }
 
    ret = 0;
    if(B && C) {
        for(int i = 0; i < 10; ++i) {
            for(int j = 0; j < 10; ++j) {
                int sum = need[i] + need[j] + need[(i + j + carry) % 10];
                if(sum > n) continue;
                bool nxt = i + j + carry >= 10;  //check next carry
                //always can continue to place B & C
                add(ret, dfs(n - sum, nxt, 1, 1));
                if(i) add(ret, dfs(n - sum, nxt, 0, 1)); //choose not to place B
                if(j) add(ret, dfs(n - sum, nxt, 1, 0)); //choose not to place C
                if(i && j) add(ret, dfs(n - sum, nxt, 0, 0)); //choose neither place
            }
        }
    } else if(B) {
        for(int i = 0; i < 10; ++i) {
            int sum = need[i] + need[(i + carry) % 10];
            if(sum > n) continue;
            bool nxt = i + carry >= 10;
            add(ret, dfs(n - sum, nxt, 1, 0));
            if(i) add(ret, dfs(n - sum, nxt, 0, 0));
        }
    } else if(C) {
        for(int j = 0; j < 10; ++j) {
            int sum = need[j] + need[(j + carry) % 10];
            if(sum > n) continue;
            bool nxt = j + carry >= 10;
            add(ret, dfs(n - sum, nxt, 0, 1));
            if(j) add(ret, dfs(n - sum, nxt, 0, 0));
        }
    } else {
        if(carry && n == need[1]) ret = 1;
    }
    return ret;
}
 
int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//  freopen("out.txt","w",stdout);
#endif
    ios_base::sync_with_stdio(0);

    int t; scanf("%d", &t);
    int kase = 0;
    while(t--) {
        scanf("%d%d", &n, &m);
        n -= 3; //subtract - & = signs' cost
        memset(dp, -1, sizeof dp);
        printf("Case #%d: %I64d\n", ++kase, dfs(n, 0, 1, 1));
    }
    return 0;
}

[数位DP][二进制性质][异或][贪心] 百度之星初赛Round 2 1003 度度熊与运算式1


题意:给一堆1,中间由运算符 $op$ 隔开,$op$ 可能是按位异或 $\oplus$ 或者$?$, 问现在把 $?$ 换成 $\oplus$ 或者 $+$ ,能让整个运算式取最大的方案结果是多少?

题解:首先注意到一点是位运算优先级比异或运算高,这样的话显然我们可以把原来的不能更换的 $\oplus$ 当作分隔,把原式分割成一些段,现在的问题就是怎么处理这些段里的符号使得最后结果最大。
继续阅读“[数位DP][二进制性质][异或][贪心] 百度之星初赛Round 2 1003 度度熊与运算式1”

[数位DP][模板][二进制性质] SDOI2016 储能表

题目链接

题意:给定n, m, k, p, 求出
\[
\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} max(i\oplus j-k)\\
最大数据范围
T = 5000, n \leq 10 ^ {18} , m \leq 10 ^ {18} , k \leq 10 ^ {18} , p \leq 10 ^ 9
\]

题解:转化成求\[\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} i\oplus j*\epsilon(i\oplus j-k) – k*\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} 1*\epsilon(i\oplus j-k)\],其中\[\epsilon(x)当x> 0时为1,否则为0\]. 这样的话可以直接数位dp计算了,有两个范围和一个约束,要计算的量也有两个,刚学到的kuangbin的板子十分的好用!!!这个甩锅给-1位的写法真的太简洁了。

error:第一个是搞错了当前位的贡献,只乘上了二进制位的权值,没有乘后面的种类数。第二个是爆int,日常long long错误
继续阅读“[数位DP][模板][二进制性质] SDOI2016 储能表”

[补题][数位dp][位运算] 2019牛客第七场 H Pair

题目链接

题意:给定ABC三个数,大小1e9,要求求出来1<=x<=A,1<=y<=B范围内满足(x&y)>C或(x^y)<C的(x,y)对数。

由于题目给的是或条件,所以还要考虑一个容斥的问题,先算出来单独满足两个条件的数目,再减去同时满足两个条件的数目,即可得到答案。先考虑单独计算两个条件的情况:

一般来说这种数位dp的框架都是类似的,在第i位时考虑前面的约束有没有“顶满”,如果顶满的话考虑当前这一位的限制,否则继续递归下去。这样的框架使得最后的递归边界处理变成了最有思维含量的一部分。这个题不同寻常的地方在于它是强制要求你同时考虑多个边界的,这是值得注意的一点。在这个题目里比较友好的一点是他对于与C的关系比对的时候是不等号,没有等号,这样的话,我们只要在dp计数的过程中一直保持可行性就可以了,到最后一步(pos=-1)只需要判断

1)前面是不是顶到C的头了,如果是的话就不行
2)前面是不是顶到了全0,因为题目要求x,y至少为1
继续阅读“[补题][数位dp][位运算] 2019牛客第七场 H Pair”

[补题][线段树][权值区间离散化] 2019牛客第七场 E Find the median

本质上是一个离散化的权值线段树,其实很简单,比赛时候没写出来是个遗憾,主要当时没考虑清楚离散化的对象,结果和之后的处理方式。

刚开始看题面里一个数据生成公式,以为是强制在线的,后来发现其实是假的,输入是可以全部离线处理出来的。这样的话就可以先把每一步中的L,R求出来了。之后的话,注意到题目的操作实质上相当于权值线段树的操作就可以计数了,我这里当时以为是add值的操作,其实只是增加了数字的个数而已。但是数字范围会很大,所以要离散化一下。这里要特别注意的是在这种区间的离散化之后,就不是原来线段树了(原来的我们所谓正常的线段树其实相当于是个点树),因此要用[l,r)这种左闭右开的区间来计算,否则二分的时候会漏掉中间的一段[mid,mid+1]。

wa点的话有一个是查询的时候参考值要用long long,因为他最大可能是4e5*1e9的,另一个是要开8倍空间,因为4e5个操作其实有8e5个l(r)值。。。
继续阅读“[补题][线段树][权值区间离散化] 2019牛客第七场 E Find the median”

[比赛补题][DP][递推计数][思维] 牛客2019多校 第一场 E ABBA

比赛时候:第一眼这不就DP吗,第二眼欸这东西怎么转移啊。。gg

当时迷了一直在想以AB和BA的数目为下标推,但是这样会有很多去重的问题。。。于是就没整出来,后来滚去推高数题了

看了叉姐的一句话题解大概懂了,推的时候直接用x和y的数目做下标,这样首先可以避免重复的问题。也就是设dp[i][j]为i个A,j个B时可用的前缀有多少种。这里面最重要的就是这个“可用的”怎么确定。这里的判断写出来很简单(i-j<=n&&j-i<=m).为什么是这样的呢?现在来考虑一个前缀,什么时候我们可以判断这个前缀一定不能作为最终答案中的某一个方案的前缀(也就是对最终答案没有任何贡献)?首先考虑凑AB,要用这个前缀的话,最终的结果是在他后面再补上(n+m-i)个A和(n+m-j)个B,注意这里的后面的排列并没有确定,也就是说我为了用到这个前缀(使答案+=它这种情况),可以尽力的去想办法摆布后面的A和B使得满足要求。那么有一个结论是在i-j>n的时候无论后面如何摆都满足不了要求:这个前缀至少可以配成j-1个AB或者BA,在加一个AB或者BA,然后如果i比j多了n个以上,那么意味着这多出来的i-j个A跟后面的配对只能配AB了,这样的话AB的总数就超过了n,由于AB和BA的总数确定的,这时候就一定不满足要求了;反之如果i-j<=n,则一定可以通过后面的调整来满足n个AB,m个BA的要求,可以自己试试看,只要注意到AB和BA的互换就行了。比如ABAB可以看出俩AB也可以看出一个AB一个BA(中间的)。。

然后dp边界是dp[i<=n][0]=1,dp[0][j<=m]=1,这个很好理解,全是A或者全是B就只有一种情况的。。。

这题还有点卡常,dp每次不要都memset,否则会tle 继续阅读“[比赛补题][DP][递推计数][思维] 牛客2019多校 第一场 E ABBA”