乱刷
(点击标题有传送QAQ)
题目大意: 给定一个序列b描述$a_1..a_{2i-1}$的中位数, 请问是否存在一个序列a满足b的描述
做法: 每次向集合中添加两个元素, 产生的效果是: 让中位数变小1, 中位数不变, 中位数变大1(此处的1表示在集合中的相对大小), 可以发现$b_i$描述的元素肯定是a中的元素, 那么, 如果集合中比$b_i$小的数中的最大值不是$b_{i-1}$ 或者比$b_i$大的数中的最小值不是$b_{i-1}$, 那么这个a就是不存在的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 int main(){ 18 #ifndef ONLINE_JUDGE 19 freopen("0.txt","w",stdout); 20 #endif 21 22 int T=read(); 23 while(T--){ 24 int n=read(); 25 vector<int> a(n); 26 for(int i=0; i<n; i++) 27 a[i]=read(); 28 29 set<int> s; 30 s.insert(a[0]); 31 bool flag = 1; 32 for(int i=1; i<n; i++){ 33 if(a[i]==a[i-1]) 34 continue; 35 auto p = s.insert(a[i]).first; 36 if(*(a[i-1]>a[i]?next(p):prev(p))!=a[i-1]){ 37 flag=0; 38 break; 39 } 40 } 41 puts(flag?"Yes":"No"); 42 } 43 44 }
题目大意: 一个长为n的全0字符串, 如果s[i-1]=1&&s[i+1]=1, 那么s[i]=1, 每次操作可以把某个0变成1, 现在求把它变成全1有多少种方案
做法: 设把字符串划分成k段, 每段通过一个0分开, 那么, 我们用dp[i][j]表示把前i个, 一共有j个1, s[i]=0的方案数, 那么$dp[i+k+1][j+k]=dp[i+k+1][j+k]+dp[i][j]*2^{k-1}*C_{j+k}^{k}$
其中$2^{k-1}$表示的是没有自动补全地填一段长为k的字符串的方案数, 而由于每一段是独立的, 只要相对顺序不变即可, $C_{j+k}^{k}$则表示这k次操作在全部j+k次操作里的位置
最后的答案是$\sum_{j=1}^n dp[n+1][j]$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 int main(){ 18 #ifndef ONLINE_JUDGE 19 freopen("0.txt","w",stdout); 20 #endif 21 22 int n=read(), mod=read(); 23 vector<vector<int>>C(n+2, vector<int>(n+2, 0)); 24 C[0][0]=1; 25 for(int i=1; i<=n+1; i++){ 26 C[i][0]=1; 27 for(int j=1; j<=i; j++) 28 C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; 29 } 30 31 vector<vector<int>> dp(n+2, vector<int>(n+1, 0)); 32 auto power = [&](int a, int b){ 33 int res=1; 34 for(; b; a=1ll*a*a%mod, b>>=1) 35 if(b&1) 36 res=1ll*a*res%mod; 37 return res; 38 }; 39 40 dp[0][0]=1; 41 for(int i=0; i<=n; i++){ 42 for(int j=0; j<=i; j++) 43 for(int k=1; i+k+1<=n+1; k++) 44 dp[i+k+1][j+k]=(dp[i+k+1][j+k] 45 +1ll*C[j+k][k]*power(2, k-1)%mod*dp[i][j]%mod)%mod; 46 } 47 48 int ans = 0; 49 for(int i=1; i<=n; i++) 50 ans=(ans+dp[n+1][i])%mod; 51 printf("%d\n", ans); 52 }
题目大意: 给一个序列a和一个集合B, 每次可以从集合B中挑一个元素x, 然后将a中长为x的连续子段进行一次区间取反, 求最大的$\sum a_i$
做法: 考虑B中两个元素x, y, 进行一次x和一次y之后, 等价于对一段$|x-y|$的区间取反, 也就是说, 我们所有的取反操作等价于对$gcd(b_1...b_m)$取反
考虑a序列可以到哪些状态。 我们设一段字符串$s=0....0/1...1$ 其中1表示乘1, 0表示乘-1, 我们发现对一段长为g的区间取反, 设$f(c)=Xor s[i], i\ mod\ g==c$, 那么对于所有的f(0..g-1), 都是相等的。
也就是说, 所有的位置是独立的, 只要s‘’对应的f相同, 那么s可以通过一系列操作变为s'
于是可以通过dp来解决这个问题
dp[i][j]表示前i位, 所有x==i%g的位置的和的最大值
那么有
dp[i][j]=max(dp[i-g][j^1]-a[i], dp[i-g][j]+a[i])
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 inline void solve(){ 18 19 int n=read(), m=read(); 20 vector<int> a(n); 21 for(int i=0; i<n; i++) 22 a[i]=read(); 23 int g=read(); 24 for(int i=1; i<m; i++) 25 g=__gcd(g, (int)read()); 26 27 vector<vector<ll>> dp(n, vector<ll>(2)); 28 for(int i=0; i<n; i++){ 29 if(i<g) 30 dp[i][0]=a[i], 31 dp[i][1]=-a[i]; 32 else 33 dp[i][0]=max(dp[i-g][0]+a[i], dp[i-g][1]-a[i]), 34 dp[i][1]=max(dp[i-g][1]+a[i], dp[i-g][0]-a[i]); 35 } 36 ll res[2]={0}; 37 for(int i=n-g; i<n; i++) 38 for(int j=0; j<2; j++) 39 res[j]+=dp[i][j]; 40 printf("%lld\n", max(res[0], res[1])); 41 42 } 43 44 int main(){ 45 #ifndef ONLINE_JUDGE 46 freopen("0.txt","w",stdout); 47 #endif 48 49 int T=read(); 50 while(T--) 51 solve(); 52 53 }
题目大意: 给定一张n个点, m条边的无向图, 询问k次, 每次给个x, 问你把所有边权改成|wi-x|后, 整个图的最小生成树边权和多少, 询问之间独立 k 10^7
做法: 其实应该都做过这类题: 给定一条数轴, 选一个点使得$\sum |a_i-x|$最小
实际上是差不多的, 但是不同的是这里的点需要保持连通性
可以发现除了边权对应的点之外, 他们的中点就是一个最小生成树的突变点, 所以我们可以把所有的边权, 以及他们的中点取出来, 然后先把这些点对应的最小生成树跑出来, 然后, 对于x的答案, 从我们已经确定答案的点中, 找一个离他最近的点, 两者生成树等价, 原因跟上面的例题是一样的, (在中点中间随意取, \sum |a_i-x|不变)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 struct node { 18 int u, v, w, id; 19 }a[305]; 20 21 int f[55]; 22 int find(int x){return f[x]==x?x:f[x]=find(f[x]);} 23 24 int main(){ 25 #ifndef ONLINE_JUDGE 26 freopen("0.txt","w",stdout); 27 #endif 28 29 vector<int> v; 30 int n=read(), m=read(); 31 for(int i=1; i<=m; i++) 32 a[i].u=read(), a[i].v=read(), a[i].w=read(), a[i].id=i; 33 34 for(int i=1; i<=m; i++){ 35 v.pb(a[i].w); 36 for(int j=1; j<=m; j++) 37 v.pb((a[i].w+a[j].w)/2-1), 38 v.pb((a[i].w+a[j].w)/2), 39 v.pb((a[i].w+a[j].w)/2+1); 40 } 41 42 sort(v.begin(), v.end()); 43 v.erase(unique(v.begin(), v.end()), v.end()); 44 45 vector<vector<int>> st(v.size()); 46 47 for(int k=0; k<v.size(); k++){ 48 int x=v[k]; 49 sort(a+1, a+m+1, [&](node a, node b){ 50 return abs(a.w-x)<abs(b.w-x); 51 }); 52 53 for(int i=1; i<=n; i++) 54 f[i]=i; 55 for(int i=1; i<=m; i++){ 56 int u=find(a[i].u), v=find(a[i].v); 57 if(u!=v) 58 f[v]=u, st[k].pb(a[i].id); 59 } 60 } 61 62 sort(a+1, a+m+1, [](node a, node b){ 63 return a.id<b.id; 64 }); 65 66 auto calc = [&](int x){ 67 int l=0, r=v.size()-1, pos=0; 68 while(l<=r){//第一个比x大的位置 69 int mid=l+r>>1; 70 if(v[mid]<=x) 71 pos=mid, l=mid+1; 72 else 73 r=mid-1; 74 } 75 76 if(pos+1<v.size()&&v[pos+1]-x<x-v[pos]) 77 ++pos; 78 79 ll ans = 0; 80 for(int e: st[pos]) 81 ans = ans + abs(x-a[e].w); 82 return ans; 83 84 }; 85 86 int p=read(), k=read(), a=read(), b=read(), c=read(), x; 87 ll ans=0; 88 89 for(int i=1; i<=k; i++){ 90 if(i<=p) 91 x=read(); 92 else 93 x=(1ll*x*a%c+b)%c; 94 ll tmp = calc(x); 95 // printf("%d %lld\n", x, tmp); 96 ans^=tmp; 97 } 98 99 printf("%lld\n", ans); 100 }
E2. Distance Tree (hard version)
题目大意: 给定一棵以1为根的有根树, 边权为1, 询问在树中添加一条边权为i的边之后(i:1~n), 所有点到1的距离的最大值最小是多少
做法: 首先, 添加的这条边肯定是连接(1, u)的, 设边权i对应的答案为ans, 那么, 对于每一棵子树u, 找到两个节点v1, v2使得他们是子树u的一条直径(depv1>ans, depv2>ans), 那么, 最好的方法就是连在v1和v2的中点上, 此时能让这条直径对半分, 使得这一棵子树内的点到1的最大值最小。 当i对应的答案是ans时, ans满足ans>=最大深度, 或者满足$\lceil \frac{g[ans]}{2} \rceil + x \leq ans$其中g[ans]表示的是, 存在d[v]>ans的子树内, 最远的两个点的距离。 可以发现随着ans的增大, g[ans]是减小的。 所以g[ans]可以$O(N)/O(Nlog_2N)$求
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 inline void solve(){ 18 int n=read(); 19 20 vector<vector<int>> e(n); 21 vector<int> f(n, 0), d(n, 0), g(n, 0); 22 for(int i=1; i<n; i++){ 23 int u=read(), v=read(); 24 --u; --v; 25 e[u].pb(v); 26 e[v].pb(u); 27 } 28 29 std::function<void(int, int)> dfs = [&](int u, int fa){ 30 int mx1=d[u], mx2=d[u];//最深的两个点 31 g[u]=d[u]; 32 for(int v: e[u]){ 33 if(v==fa) 34 continue; 35 d[v]=d[u]+1; 36 dfs(v, u); 37 if(g[v]>mx1) 38 mx2=mx1, mx1=g[v]; 39 else if(g[v]>mx2) 40 mx2=g[v]; 41 g[u]=max(g[u], g[v]);//最深的点 42 } 43 int tmp = min(mx1, mx2)-1, //找>ans的两个点 44 dis = mx1+mx2-2*d[u]; 45 if(~tmp) 46 f[tmp] = max(f[tmp], dis); 47 }; 48 49 dfs(0, -1); 50 51 for(int i=n-2; ~i; i--) 52 f[i] = max(f[i+1], f[i]); 53 54 int ans = 0; 55 for(int i=1; i<=n; i++){ 56 while(ans<g[0]&&((f[ans]+1)/2+i)>ans) 57 ++ans; 58 printf("%d ", ans); 59 } 60 puts(""); 61 62 // puts(""); 63 } 64 65 int main(){ 66 #ifndef ONLINE_JUDGE 67 freopen("0.txt","w",stdout); 68 #endif 69 70 int T=read(); 71 while(T--) 72 solve(); 73 74 }
$d\leq 5$, 所以我们发现 , 对于每个$i(a_i==-1)$, 对于$j, j<i$, p[j]==p[i]的范围在$[i-2d, i-1]$中
我们设dp[i][S]表示第i个位置, [i-2d, i]选数的状态为S的方案数
可以发先, 转移的话, 直接把S>>1就是当前的状态了, S中的从低往高第j位表示i-j-d有没有填
然后就直接转移
效率$O(N2^{2d+1})$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 const int mod = 998244353; 18 inline void add(int &x, int y){ 19 x+=y; 20 if(x>=mod) x-=mod; 21 if(x<0) x+=mod; 22 } 23 24 int main(){ 25 #ifndef ONLINE_JUDGE 26 freopen("0.txt","w",stdout); 27 #endif 28 // putchar('\x43'); putchar('\n'); 29 // printf("%d\n", sizeof("\x43")); 30 // printf("%d\n", sizeof("")); 31 // putchar('\x4'); 32 int n, d; 33 n=read(); d=read(); 34 vector<int> a(n), vis(n,0); 35 int dp[505][1<<11]={0}; 36 for(int i=0; i<n; i++){ 37 a[i]=read(); 38 if(a[i]==-1); 39 else 40 vis[--a[i]]=1; 41 } 42 //对每个-1, 开dp[i][j]存一下, 当前这一位往前2d位选了j 43 //可以发现只需要存最近的10个单位内的-1 44 //存一下最近10个-1用了什么数字 45 if(a[0]!=-1) 46 dp[0][0]=1; 47 else{ 48 for(int j=d; j>=-d; j--){ 49 int x=j; 50 if(j>=n) 51 continue; 52 if(j<0) break; 53 if(!vis[x]) 54 dp[0][(1<<(j+d))]=1; 55 } 56 } 57 for(int i=1; i<n; i++) 58 for(int j=0; j<(1<<11); j++){//枚举前面几个-1的状态 59 int k = j>>1; 60 if(a[i]!=-1) 61 add(dp[i][k],dp[i-1][j]); 62 else{ 63 for(int p=d; p>=-d; p--){ 64 int x=p+d;//第几位 65 // printf("%d\n", x); 66 if(i+p>=n)//值为多少 67 continue; 68 if(i+p<0) 69 break; 70 if(((k>>x)&1)||vis[i+p]) continue; 71 //这个值, 之前的2d个没用过, 且, 没有确定的值用过 72 add(dp[i][k^(1<<x)], dp[i-1][j]); 73 } 74 } 75 } 76 int ans = 0; 77 for(int j=0; j<(1<<11); j++) 78 // if(dp[n-1][j]) 79 // printf("%d %d\n", j, dp[n-1][j]); 80 add(ans, dp[n-1][j]); 81 printf("%d\n", ans); 82 }
交互题, 首先我们设左下右上$(x, y), (a, b)$
那么询问$(1, 1), (1, 10^9), (10^9, 1)$, 可以得到三组独立方程
$$x-1+y-1=d1(1)\\ x-1+10^9-b=d2(2)\\ 10^9-a+y-1=d3(3)\\$$
显然, 有4个未知数, 3组方程是不够的
此时, 如果我们把, x二分出来, 那么, 这三组方程就够了
如何二分, 考虑$(1, i)$的答案
很明显他是一条$[1, x]$递减, $[x, \frac{x+a}{2}]$递增, $[\frac{x+a}{2}, a]$递减, $[a, 10^9]$递增的折线, 而且斜率为$\pm 1$
所以, 你拿一条$y=d1+1-x$的曲线去拟合他, 跟他重合的部分, 只会有$[1, x]$这一部分, 那么二分就可以了
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 const int mod = 1e9+7; 18 inline void add(int &x, int y){ 19 x+=y; 20 if(x>=mod) x-=mod; 21 if(x<0) x+=mod; 22 } 23 24 inline int calc(int x, int y, int a, int b){ 25 return abs(a-x)+abs(b-y); 26 } 27 28 inline int dist(int x, int y){ 29 return min({calc(x, y, 2, 3), calc(x, y, 4, 5), calc(x, y, 2, 5), calc(x, y, 4, 3)}); 30 } 31 32 int main(){ 33 #ifndef ONLINE_JUDGE 34 // freopen("0.txt","w",stdout); 35 #endif 36 printf("? 1 1\n"); 37 fflush(stdout); 38 int d1=dist(1, 1); 39 printf("%d\n", dist(1, 1)); 40 scanf("%d", &d1); 41 42 printf("? 1000000000 1\n"); 43 fflush(stdout); 44 int d2=dist(1000000000, 1); 45 printf("%d\n", dist(1000000000, 1)); 46 scanf("%d", &d2); 47 48 printf("? 1 1000000000\n"); 49 fflush(stdout); 50 int d3=dist(1, 1000000000); 51 printf("%d\n", dist(1, 1000000000)); 52 scanf("%d", &d3); 53 54 int l=1, r=1e9, x=0; 55 while(l<=r){ 56 int mid=(l+r)>>1; 57 printf("? %d 1\n", mid); 58 fflush(stdout); 59 int dis=dist(mid, 1); 60 printf("%d\n", dist(mid, 1)); 61 scanf("%d", &dis); 62 if(d1-dis==mid-1) 63 l=mid+1, x=mid; 64 else 65 r=mid-1; 66 } 67 int y, a, b; 68 // printf("??? %d %d %d\n", d1, d2, d3); 69 y=d1-x+2; a=1000000000-d2+y-1; b=1000000000+x-d3-1; 70 printf("! %d %d %d %d\n", x, y, a, b); 71 fflush(stdout); 72 }
有这么一个结论, 在答案序列中, 一个点只会经过一次, 要不然就陷入循环了
那么, 我们用set维护一下某个点有没有跳到过, 然后bfs一下, 这样每个点只会入队1次, 出队1次
记录一下, 每个点的跳板在哪里, 从哪里来即可, 即i(从哪里来)->i-a[i](跳板)->i-a[i]+b[i-a[i]]
当然也可以线段树优化最短路建图把这题冲了
#include<bits/stdc++.h> using namespace std; #define ll long long #define pb push_back inline ll read() { ll x = 0, f = 1; char ch = getchar(); for(; ch < '0' || ch>'9'; ch = getchar()) if(ch == '-') f = -f; for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0'; return x * f; } #define ln endl const int mod = 1e9+7; inline void add(int &x, int y){ x+=y; if(x>=mod) x-=mod; if(x<0) x+=mod; } const int N=3e5+5; int n, a[N], b[N], vis[N], pre[N], from[N]; int main(){ #ifndef ONLINE_JUDGE freopen("0.txt","w",stdout); #endif n=read(); for(int i=1; i<=n; i++) a[i]=read(); for(int i=1; i<=n; i++) b[i]=read(); for(int i=0; i<=n; i++) vis[i]=-1; queue<int> q; vis[n]=0; q.push(n); set<int> s; for(int i=0; i<n; i++) s.insert(i); while(!q.empty()){ // puts("qwq"); int now = q.front(); q.pop(); int l=now-a[now], r=now; set<int>::iterator L=s.lower_bound(l), R=s.upper_bound(r); //能跳到这个区间内的任何一个点 for(set<int>::iterator i=L; i!=R; i++){ // printf("%d->%d\n", now, *i); if(vis[*i+b[*i]]==-1){ vis[*i+b[*i]]=vis[now]+1; pre[*i+b[*i]]=now; from[*i+b[*i]]=*i; q.push(*i+b[*i]); } } s.erase(L, R); } // for(int i=0; i<n; i++) // printf("%d ", vis[i]); if(vis[0]==-1) return puts("-1"), 0; int x=0; vector<int> ans; while(x!=n) ans.pb(from[x]), x=pre[x]; reverse(ans.begin(), ans.end()); printf("%u\n", ans.size()); for(int x:ans) printf("%d ", x); putchar('\n'); }
可以有这么一个结论: b是按递增顺序插入a的
假如不满足, 设b[i]<b[i+1], 但是插入的位置在i+1之后
对于一个位置pos, 在x插入的贡献是, $[1,pos)$中比x大的+$[pos, n]$中比x小的
考虑b[i]插入到pos1, b[i+1]插入到pos2, 假设pos1>pos2, 那么对于第一部分, 比b[i+1]大的肯定比b[i]大, 对于第二部分, 比b[i]小的肯定比b[i+1]小, 而且还多了一对(b[i+1], b[i])
此时我们把他们翻转过来, 势必能够造成, 答案的减少。
设dp[v][x]表示v插入到x前面的代价, 这个值=[1, x)中比v大的+[x, n]中比v小的
当我们查询到b[i]的时候, 把a序列中比b[i]大的当做1, 把a序列中比b[i]小的当做0
可以发现, 1的数量只会减少, 0的数量只会增加
那么, 我们只需要设一开始都是1, 然后从a的最小值开始往a的最大值跳, 把1改成0即可
我们考虑把1改成0的贡献, 我们把x这个位置改成了0, 对于[1, x), 他们的后面多了一个0, 则dp值均增加1
对于[x, n], 他们前面少了一个1, 则dp值均减少1
这个可以用线段树方便的维护, 然后只需要查询在$dp[i], i\in [1, n]$的最小值即可, 对于插入到最后, 他的值就是a中比他大的数的个数, 因为需要求一下a的逆序对, 所以可以顺便统计一下
#include<bits/stdc++.h> using namespace std; #define ll long long #define pb push_back inline ll read() { ll x = 0, f = 1; char ch = getchar(); for(; ch < '0' || ch>'9'; ch = getchar()) if(ch == '-') f = -f; for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0'; return x * f; } #define ln endl const int mod = 1e9+7; inline void add(int &x, int y){ x+=y; if(x>=mod) x-=mod; if(x<0) x+=mod; } const int N=1e6+5; int n, m, a[N], b[N], id[N]; int t[N<<2], lzy[N<<2]; void up(int rt){ t[rt]=min(t[rt<<1], t[rt<<1|1]); } void build(int l, int r, int rt){ lzy[rt]=0; if(l==r){ t[rt]=l-1; return; } int mid=(l+r)>>1; build(l, mid, rt<<1); build(mid+1, r, rt<<1|1); up(rt); } void down(int rt){ if(lzy[rt]){ t[rt<<1]+=lzy[rt]; t[rt<<1|1]+=lzy[rt]; lzy[rt<<1]+=lzy[rt]; lzy[rt<<1|1]+=lzy[rt]; lzy[rt]=0; } } void add(int L, int R, int v, int l, int r, int rt){ if(L<=l&&r<=R){ t[rt]+=v; lzy[rt]+=v; return; } down(rt); int mid = (l+r)>>1; if(L<=mid) add(L, R, v, l, mid, rt<<1); if(R>mid) add(L, R, v, mid+1, r, rt<<1|1); up(rt); } vector<int> c; int bit[N<<1]; inline int lowbit(int x){return x&-x;} inline void add(int x){for(;x<=c.size();x+=lowbit(x)) bit[x]++;} inline int ask(int x){int res=0; for(;x;x-=lowbit(x)) res+=bit[x]; return res;} int main(){ #ifndef ONLINE_JUDGE freopen("0.txt","w",stdout); #endif int T=read(); while(T--){ n=read(); m=read(); c.clear(); build(1, n, 1); for(int i=1; i<=n; i++) a[i]=read(), c.push_back(a[i]); for(int i=1; i<=n; i++) id[i]=i; sort(id+1, id+n+1, [&](int x, int y){ return a[x]<a[y]; }); for(int i=1; i<=m; i++) b[i]=read(), c.push_back(b[i]); sort(c.begin(), c.end()); c.erase(unique(c.begin(), c.end()), c.end()); for(int i=1; i<=c.size(); i++) bit[i]=0; int now = 0; ll ans = 0; for(int i=1; i<=n; i++) a[i]=lower_bound(c.begin(), c.end(), a[i])-c.begin()+1, ans=ans+ask(c.size())-ask(a[i]), add(a[i]); for(int i=1; i<=m; i++) b[i]=lower_bound(c.begin(), c.end(), b[i])-c.begin()+1; sort(b+1, b+m+1); vector<int> v; //dp[i]表示在i之前插入x的代价 //对于dp[n+1], 则为1..n中比i大的数 for(int i=1; i<=m; i++){ while(now+1<=n&&a[id[now+1]]<=b[i]){ int x = id[++now]; if(a[x]==b[i]) v.pb(x); else add(1, x, 1, 1, n, 1);//多了一个比自己大的 if(x+1<=n) add(x+1, n, -1, 1, n, 1);//少了一个比自己小的 } ans = ans + min(t[1], ask(c.size())-ask(b[i])); if(b[i]!=b[i+1]){ for(int x: v) add(1, x, 1, 1, n, 1); v.clear(); } } printf("%lld\n", ans); } }
首先考虑f(l, r)怎么计算, 考虑贪心, 从r开始往前, 如果a[r-1]>a[r], 那么a[r-1]至少要拆成$\lceil \frac{a[r-1]}{a[r]} \rceil$个数, 贪心地让最小的那个数最大, 则最小的那个数为$\lfloor \frac{a[r-1]}{\lceil \frac{a[r-1]}{a[r]} \rceil} \rfloor$(因为要让这若干个数尽量的相同, 所以只能给每个数都按下取整分配)
然后, 我们知道了一段f(l, r)怎么计算之后, 就可以开始dp了, dp[i][x]表示以i为左端点, 最小值为x的所有区间的值的和, 令g[i][x]表示以i为左端点, 最小值为x的区间的数量。
可以推出
dp[i][$\lfloor \frac{a[i]}{\lceil \frac{a[i]}{x} \rceil} \rfloor$] = $\sum_x$ dp[i+1][x] + g[i+1][x]*($\lceil \frac{a[r-1]}{a[r]} \rceil -1)$
g[i][$\lfloor \frac{a[i]}{\lceil \frac{a[i]}{x} \rceil} \rfloor$] = $\sum_x$ g[i+1][x]
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define pb push_back 5 6 inline ll read() { 7 ll x = 0, f = 1; char ch = getchar(); 8 for(; ch < '0' || ch>'9'; ch = getchar()) 9 if(ch == '-') f = -f; 10 for(; ch >= '0' && ch <= '9'; ch = getchar()) 11 x = x * 10 + ch - '0'; 12 return x * f; 13 } 14 15 #define ln endl 16 17 const int N=1e5+5; 18 const int mod = 998244353; 19 inline void add(int &x, int y){ 20 x+=y; 21 if(x>=mod) x-=mod; 22 if(x<0) x+=mod; 23 } 24 25 int dp[2][N], g[2][N], vis[N]; 26 vector<int> v[2]; 27 int n, a[N]; 28 29 int main(){ 30 #ifndef ONLINE_JUDGE 31 // freopen("0.in","r",stdin); 32 freopen("0.txt","w",stdout); 33 double be = clock(); 34 #endif 35 int T=read(); 36 while(T--){ 37 n=read(); 38 for(int i=1; i<=n; i++) 39 a[i]=read(); 40 for(int vv: v[n&1]) 41 dp[n&1][vv]=g[n&1][vv]=0; 42 v[n&1].clear(); v[n&1].pb(a[n]); 43 dp[n&1][a[n]]=0; g[n&1][a[n]]=1; 44 int ans=0; 45 for(int i=n-1; i; i--){ 46 for(int vv: v[i&1]) 47 dp[i&1][vv]=g[i&1][vv]=0; 48 v[i&1].clear(); v[i&1].pb(a[i]); 49 vis[a[i]]=1; 50 dp[i&1][a[i]]=0; g[i&1][a[i]]=1; 51 for(int vv: v[i&1^1]){ 52 int x=a[i]/((a[i]-1)/vv+1); 53 if(!vis[x]) 54 vis[x]=1, v[i&1].pb(x); 55 add(ans, (dp[i&1^1][vv]+1ll*g[i&1^1][vv]*((a[i]-1)/vv)%mod)%mod); 56 add(dp[i&1][x], (dp[i&1^1][vv]+1ll*g[i&1^1][vv]*((a[i]-1)/vv)%mod)%mod); 57 add(g[i&1][x], g[i&1^1][vv]); 58 } 59 for(int vv: v[i&1]) 60 vis[vv]=0; 61 } 62 printf("%d\n", ans); 63 } 64 #ifndef ONLINE_JUDGE 65 double en = clock(); 66 printf("Time: %.0lfms\n", en - be); 67 // fclose(stdin); fclose(stdout); 68 #endif 69 }