1003 Calculate
题意:给定x1,x2,y1,y2,计算
,模数为1e9+7
T <= 100, 1 <= x1 <= x2 <= 1e9, 1 <= y1 <= y2 <= 1e9
solution:
暴力展开,对于∑i / x1,能够O(1)计算
对于∑x2 / i 这项, 对10 / i 进行分析, 10 / i 的ans分别为 10 5 3 2 2 1 1 1 1 1
可以发现10 / i 的ans一共有5种:10,5,3,2,1,而不是10种,对于小的ans,出现了多次,且ans的值是连续的,对于大的ans,ans的值不连续且只出现一次
我们把ans * ans > x2 的 ans称为大值,其余ans为小值,可以算出大值和小值都是sqrt(x2)个,对于大值,都只出现1次,对于每个小值,可以O(1)计算出这个小值出现的次数
这样我们就能O(sqrt(n))计算出∑x2 / i 这项了
对于所有包含x2 / i 这种类型的项,我们按也ans = x2 / i 的值分块,分成sqrt(x2)个大值块和sqrt(x2)个小值块
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const long long MOD = 1e9+7;
long long qpow(long long n,long long p){
long long res = 1;
for(;p;p>>=1,n = n * n % MOD){
if(p&1) res = res * n % MOD;
}
return res;
}
long long inv(long long x) {
return qpow(x,MOD-2);
}
long long suan01(long long x1,long long x2){// (i/x1)
if(x2 < x1) return 0;
long long up = x2 / x1 - 1;
long long re = x2 - x1 + 1 - x1 * up;
long long res = 0;
res += re * (up + 1) % MOD;
if(up) res += x1 * (up) % MOD * (up + 1) % MOD * inv(2) % MOD;
res %= MOD;
return res;
}
long long suan1(long long x1,long long x2){// (i/x1)^2
long long up = x2 / x1 - 1;
long long re = x2 - x1 + 1 - x1 * up;
long long res = 0;
res += re * (up+1) % MOD * (up+1) % MOD;
if(up) res += x1 * up % MOD * (up + 1) % MOD * (up * 2 + 1) % MOD * inv(6) % MOD;
res %= MOD;
return res;
}
long long suan02(long long x1,long long x2){//(x2 / i)
long long res = 0;
long long dd = x2 / x1;
for(long long i = x1;i * i <= x2;i++){
res += dd;
dd = x2 / (i + 1);
}
long long l,r;
for(;dd;dd--){
l = x2 / (dd+1) + 1;
l = max(l,x1);
r = x2 / dd;
res += (r - l + 1) * dd;
}
res %= MOD;
return res;
}
long long suan2(long long x1,long long x2){// (x2 / i) ^ 2
long long res = 0;
long long dd = x2 / x1;
for(long long i = x1;i * i <= x2;i++){
res += dd * dd % MOD;
dd = x2 / (i + 1);
}
long long l,r;
res %= MOD;
for(;dd;dd--){
l = x2 / (dd + 1) + 1;
l = max(l,x1);
r = x2 / dd;
res += (r - l + 1) * dd * dd ;
}
res %= MOD;
return res;
}
long long suan3(long long x1,long long x2){//2 * (i / x1) * (x2 / i)
long long res = 0;
long long dd = x2 / x1;
for(long long i = x1;i * i <= x2;i++){
res += dd * 2 * (i / x1);
dd = x2 / (i + 1);
}
long long l,r;
for(;dd;dd--){
l = x2 / (dd + 1) + 1;
l = max(l,x1);
r = x2 / dd;
res += dd * 2 * ((suan01(x1,r) - suan01(x1,l-1) + MOD) % MOD);
}
res %= MOD;
return res;
}
int main()
{
int T;
cin>>T;
long long x1,x2,y1,y2;
while(T--){
scanf("%lld%lld%lld%lld",&x1,&x2,&y1,&y2);
long long ans = 0;
long long res1 = suan01(x1,x2);
long long res2 = suan02(x1,x2);
long long res3 = suan01(y1,y2);
long long res4 = suan02(y1,y2);
ans += (y2 - y1 + 1) * (suan1(x1,x2) + suan2(x1,x2)) % MOD + (x2 - x1 + 1) * (suan1(y1,y2) + suan2(y1,y2)) % MOD;
ans %= MOD;
ans += (y2 - y1 + 1) * suan3(x1,x2) + (x2 - x1 + 1) * suan3(y1,y2);
ans %= MOD;
ans += (res1 * res3 + res1 * res4 + res2 * res3 + res2 * res4) * 2;
ans %= MOD;
printf("%lld
",ans);
}
return 0;
}
1009 Sequence
题意:给定a[n], 一个序列的贡献为在这个序列里只有一个的数的个数,要求把a[n]分成m份子序列(子序列连续),求m份子序列的贡献之和最大是多少
1 <= a[i] <= n, 2 <= m <= min(10,n),n <= 2e5
思路:
想法一:容易想到dp,用dp[ i ][ j ]表示只考虑前1 ~ i 个数,用了 j 个隔板(即分成了j+1份子序列)的最大贡献
容易得到递推式:
for(int i = 1; i<= n;i++){
for(int j = 1;j <= m && j < i;j++){
for(int pos = j;pos < i;pos++){
dp[i][j] = dp[pos][j-1] + cal(pos + 1,i);
}
}
}
这个递推式的复杂度至少是O(n^2m)的,cal(pos + 1,i)表示从 pos+1 到 i 这一段连续的子序列的贡献,可以看出这个递推式不仅复杂度很高,而且cal(pos,i)也比较难算,所以我们要对这个递推式进行优化
这个递推式虽然不优,但是我们能看出上式中枚举pos就是枚举最后一个隔板放的位置
在对这个递推式进行优化之前,为了更好地研究这个递推式,我们把dp数组加上一维
用dp[ i ][ pos ][ j ] 表示只考虑前1 ~ i 个数,用了 j 个隔板,且最后一个隔板放在 pos 位置上 的最大贡献
再用 f[ i ][ j ] 表示只考虑前1 ~ i 个数,用了 j 个隔板的最大贡献(即之前的dp[ i ][ j ]数组)
递推式就变为:
for(int i = 1; i<= n;i++){
for(int j = 1;j <= m && j < i;j++){
for(int pos = j;pos < i;pos++){
dp[i][pos][j] = f[pos][j-1] + cal(pos + 1,i);
f[i][j] = max(f[i][j],dp[i][pos][j]);
}
}
}
这个递推式还是很难看,我们想让dp[ i ][ pos ][ j ]从dp[ i-1 ][ pos ][ j ]转移过来
这样,递推式可以写成:
for(int i = 1; i<= n;i++){
for(int j = 1;j <= m && j < i;j++){
for(int pos = j;pos < i;pos++){
if(pos < i - 1) dp[i][pos][j] = dp[i-1][pos][j] + check(pos+1,i);
else dp[i][pos][j] = f[pos][j-1] + 1;//pos == i - 1时,只能多加一个隔板
f[i][j] = max(f[i][j],dp[i][pos][j])
}
}
}
其中check(pos+1,i) = cal(pos+1,i) - cal(pos+1,i-1),即表示从pos+1 到 i-1 的这一段连续子序列,加上第i位后,其贡献的变化,也就是加上第 i 位后对贡献的影响
check(pos+1,i) 比cal(pos+1,i)好算很多,能感觉很有特征,我们对其进行研究
如果pos+1 到 i-1 的这段子序列里没有和a[i]相同的数,那么加上第 i 位后贡献+1
如果pos+1 到 i-1 的这段子序列里出现了一个和a[i]相同的数,那么加上第 i 位后贡献-1
如果pos+1 到 i-1 的这段子序列里出现了两个及以上的和a[i]相同的数,那么加上第 i 位后贡献不变
也就是check(pos+1,i)只有三个值:-1,0,1,且和pos的取值有关:
pos >= pre[i],check(pos+1,i) = 1
pos >= pre[pre[i]] && pos < pre[i],check(pos+1,i) = -1
//pre[i] 表示 a[i] 上一次出现的位置
这样我们就可以用线段树的区间修改来优化了
我们建m棵线段树,第 j 棵线段树表示用了 j 个隔板的情况,线段树上每个叶子的位置表示的是pos的位置,i 每加1,表示对线段树进行一轮的修改
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 2e5+7;
int a[MAXN];
int pos[MAXN];
int pr[MAXN],prpr[MAXN];//恭喜prpr数组诞生
//dp[i][pos][j];
//f[i][j];
int f[MAXN][10];
struct NODE{
int l,r,mid;
int ma,add;
}tree[MAXN*4];
void build(int pos,int l,int r){
tree[pos].l = l;tree[pos].r = r;tree[pos].mid = l + r >> 1;
tree[pos].ma = 0;tree[pos].add = 0;
if(l == r) return;
int mid = l + r >> 1;
build(pos<<1,l,mid);
build(pos<<1|1,mid+1,r);
}
void update(int pos){
tree[pos].ma = max(tree[pos<<1].ma,tree[pos<<1|1].ma);
}
void pd(int pos){
int add = tree[pos].add;
if(!add) return;
tree[pos<<1].add += add;
tree[pos<<1|1].add += add;
tree[pos<<1].ma += add;
tree[pos<<1|1].ma += add;
tree[pos].add = 0;
}
void ADD(int pos,int l,int r,int add){
if(tree[pos].l == l && tree[pos].r == r) {
tree[pos].add += add;
tree[pos].ma += add;
return;
}
pd(pos);
int mid = tree[pos].mid;
if(r <= mid) ADD(pos<<1,l,r,add);
else if(l > mid) ADD(pos<<1|1,l,r,add);
else{
ADD(pos<<1,l,mid,add);
ADD(pos<<1|1,mid+1,r,add);
}
update(pos);
}
int Q_ma(int pos,int l,int r){
if(tree[pos].l == l && tree[pos].r == r) return tree[pos].ma;
pd(pos);
int mid = tree[pos].mid;
if(r <= mid) return Q_ma(pos<<1,l,r);
else if(l > mid) return Q_ma(pos<<1|1,l,r);
else return max(Q_ma(pos<<1,l,mid),Q_ma(pos<<1|1,mid+1,r));
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m)){
m--;//分成m块,放m-1个隔板
for(int i = 0;i <= n;i++) {
pos[i] = 0;
pr[i] = 0;
}
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
pr[i] = pos[a[i]];
pos[a[i]] = i;
prpr[i] = pr[pr[i]];
}
int res = 0;
for(int i = 1;i <= n;i++){//先预处理不用隔板的情况
if(prpr[i]);
else if(pr[i]) res--;
else res++;
f[i][0] = res;
}
for(int j = 1;j <= m;j++){
build(1,1,n);
for(int i = j + 1;i <= n;i++){
int l,r;
l = max(j,pr[i]),r = i-2;
if(r >= l) ADD(1,l,r,1);
l = max(j,prpr[i]),r = pr[i] - 1;
if(r >= l) ADD(1,l,r,-1);
ADD(1,i-1,i-1,f[i-1][j-1] + 1);//pos = i-1时,只能从j-1个隔板转移过来
f[i][j] = Q_ma(1,j,i-1);
}
}
printf("%d
",f[n][m]);
}
return 0;
}