18.8.31 考试总结
这道题就是打表找规律 然而我打表并没有找到规律... yyyuuudalao就找到了tqltql%%%
打表发现 $ A $ 数列是这样子1 1 ,2 ,2 ,6
1 1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 11 12 12 12 13 14 14 15 16 16 16 16 16...
$ t[i] $ 表示 $ i $ 这个数出现次数 然后发现 $t[i]$ 为 $i$ 所含的因子 $2$ 个数 $+ 1$ ($1$除外)
也就是说每个数如果多一个 $2$ 那么他就会多出现一次 并且这些数呈等差数列
然后就可以通过除以 $2$ 的次幂来计算这些多出现的数的和
现在的问题就变成了只要知道这个地方的数是什么 就可以求出它的前缀和 那么问题就是给你一个位置
让你求出这个位置上的值是什么 同样地方法 可以求出某个数及他前面共有多少个数 并且这玩意儿是递增的
二分就可以了 然后注意不要爆long long 了
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod = 1e9 + 7; ll L,R,inv = 500000004; ll find_sum(ll x) { ll kk = 1,num = 0; while(kk <= x) { num += x / kk; if(num > 1e18) break; kk *= 2; } return num + 1; } ll dc(ll a,ll b,ll s) { return (a + b) % mod * s % mod * inv % mod; } ll solve(ll x) { if(x == 0) return 0; if(x == 1) return 1; if(x == 2) return 2; ll l = 1,r = x,ax = x - 1; while(l <= r) { ll mid = (l + r) >> 1; ll ss = find_sum(mid); if(ss < x) ax = mid,l = mid + 1; else r = mid - 1; } ll k = 1,ans = 1; while(k <= ax) { ans = (ans + dc(k,ax / k * k,(ax / k)% mod)) % mod; k *= 2; } ll ss = find_sum(ax); ans = (ans + (ax + 1) % mod * (x - ss) % mod) % mod; return ans; } int main( ) { freopen("me.in","r",stdin); freopen("me.out","w",stdout); scanf("%I64d%I64d",& L,& R); cout << (solve(R) - solve(L - 1) + mod) % mod << endl; }
这道题是一道dp 神tmyyyuuudalao又做出来了tqltqltql%%%%%%%%%%
$dp[i][j]$ 表示此时填充了 $i$ 个数 利润为 $j$ 的方案数 那么在转移之前需要知道几个性质
1.我们从小到大放数进去 这样子每次进来的数所产生的收益肯定是他到两边界的 $min$ 值
2.区间之间的合并是互不影响的 因为原左区间的右边界相当于现在放进去的最大值 右区间的左边界也是一样的(从小往大放)
3.一个区间一定的不管它放什么数所产生的方案数都是一样的因为他们的相对大小是一样的 区间内没有重复的数
4.别看题目给你的x那么大 它实际的x是非常小的 因为 $n$ 小于80 每次最多要这个区间的一半 区间每次减半 所以不会超过300
所以就可以转移了
$dp[i][k] += ∑(dp[j][s] * dp[i - j][k - s] * c[i][j])$然后枚举 $j$ 和 $s$ 就可以了 $c[i][j]$ 表示组合数C $i$ 个里面选 $j$ 个
(因为对于这个玩意儿它是可以乱放的 方案数不受影响)
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod = 998244353; ll dp[90][300],c[100][100],p[105],inv_p[105]; int x,ma[200],n; void init( ) { c[0][0] = 1; for(int i = 0;i <= n;i ++) for(int j = 0;j <= i;j ++) { if(j == 0 || i == j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; } } void solve( ) { dp[0][0] = 1; dp[1][1] = 1; ma[1] = 1; for(int i = 2;i <= n;i ++) for(int j = 0;j < i;j ++) { for(int s = j;s <= ma[j];s ++) for(int k = i - j - 1;k <= ma[i - j - 1];k ++) { int cc = min(i - j,j + 1); ma[i] = max(ma[i],s + k + cc); dp[i][s + k + cc] = (dp[i][s + k + cc] + c[i - 1][j] * dp[j][s] % mod * dp[i - j - 1][k]) % mod; } } printf("%I64d",dp[n][x]); } int main( ) { freopen("good.in","r",stdin); freopen("good.out","w",stdout); scanf("%d%d",& n,& x); init( ); solve( ); }
这道题第一眼望过去 哎呀这不是背包吗嘻嘻嘻 然后定睛一看x范围是1e18...
行吧我走 :))
因为 $A$ 的范围是非常小的 所以可以考虑余数
对于所有物品装满 $x$ 的容量 可以知 $x = q1 * a[1] + q2 * a[2] + q3 * a[3]...qn * a[n]$
所以假设我们剔除 $a[1]$ 剩下的物品装的容量模 $a[i]$ 肯定与 $x$ 模 $a[i]$ 同余
那么考虑 $dp[i]$ 为剔除 $a[1]$ 之后剩下的物品所装的容量模上 $a[1]$ 余数为i的情况是否可以达到
那么剩下的空间肯定是可以通过装 $a[1]$ 来填充满的
然而这时候会出现一个问题 比如我的a是 4 6 8
然而我想要装2 的容量 而6模上4为2 所以在我们原先的设定中这个是合法的 但是这个并不合法
所以考虑 $dp[i]$ 表示能够装满达到状态的最少花费是多少 如果 $dp[i] > x$ 那么不管怎么样我们都是凑不到x的
这个转移可以用spfa搞
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll dis[100000 + 5],cmp,x; int n,q,a[10005]; bool vis[100005]; queue<int>Q; void SPFA( ) { vis[0] = true; memset(dis,0x3f3f3f,sizeof(dis)); dis[0] = 0; cmp = dis[3]; Q.push(0); while(! Q.empty( )) { int cur = Q.front( ); Q.pop( ); vis[cur] = false; for(int i = 2;i <= n;i ++) { int v = (cur + a[i]) % a[1]; if(dis[v] > dis[cur] + a[i]) { dis[v] = dis[cur] + a[i]; if(! vis[v]) { vis[v] = true; Q.push(v); } } } } } int main( ) { freopen("hungry.in","r",stdin); freopen("hungry.out","w",stdout); scanf("%d%d",& n,& q); for(int i = 1;i <= n;i ++) scanf("%d",& a[i]); sort(a + 1,a + n + 1); SPFA( ); while(q --) { scanf("%I64d",& x); if(dis[x % a[1]] == cmp || dis[x % a[1]] > x) printf("-1\n"); else printf("1\n"); } }