Gym102956 部分题解
带更。
- B. Beautiful Sequence Unraveling
- C. Brave Seekers of Unicorns
- D. Bank Security Unification
- E. Brief Statements Union
- F. Border Similarity Undertaking
- G. Biological Software Utilities
- H. Bytelandia States Union
- I. Binary Supersonic Utahraptors
- J. Burnished Security Updates
- K. Bookcase Solidity United
- M. Brilliant Sequence of Umbrellas
- N. Best Solution Unknown
感谢 @hht2005 的帮助。
B. Beautiful Sequence Unraveling
定义一个长度为 \(n\) 好序列 \(a\) 是不存在一个位置 \(1\le i<n\) 使得 \(\max\{a_1,\ldots,a_i\}=\min\{a_{i+1},\ldots,a_n\}\) 的序列。
对长度为 \(n\),值域为 \([1,k]\) 的好序列计数,对 \(p\) 取模并输出。
\(1\le n\le 400\),\(1\le k\le 10^8\),\(998244353\le p\le 10^9+9\),\(p\in\text{prime}\)。
Solution
\(k\) 是坑你的,如果考虑和 \(k\) 有关的直接凉凉。
考虑回避 \(k\),不难发现合法的序列出现的不同的数字只有 \(n\) 种,直接离散化。
考虑 \(f_{i,j}\) 表示一个长度为 \(i\) 的序列,值域为 \([1,j]\) 的好序列个数。
好序列并不好求,但考虑一个不好序列 \(a\),设其最大的位置 \(p\) 使得 \(\max\{a_1,\ldots,a_p\}=\min\{a_{p+1},\ldots,a_n\}\),容易发现一个大性质:\(a_{p+1},\ldots,a_n\) 是一个好序列,如果其不是一个好序列的话,\(p\) 便不是最大的。
枚举最大的位置 \(p\),枚举其值 \(m\) 可以得到 DP 式子:
使用前缀和优化可以做到 \(O(n^3)\)。
考虑再设 \(g_i\) 表示长度为 \(n\) 的序列,值域为 \([1,i]\) 的好序列个数:
所以答案为:\(\displaystyle \sum^n_{i=1}\binom k i g_i\)。
组合数暴力计算,时间复杂度 \(O(n^3)\)。
Code
const int N=400;
int n,K,mod,f[N+10][N+10],ans,g[N+10],sum[N+10][N+10][N+10];
void Add(int &x,int y) {
x+=y;
if(x>=mod) x-=mod;
}
void Del(int &x,int y) {
x-=y;
if(x<0) x+=mod;
}
int fpow(int x,int y) {
int ret=1;
for(;y;y>>=1) {
if(y&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
}
return ret;
}
int C(int n,int m) {
int ret=1,fm=1;
for(int i=n;i>=n-m+1;i--) ret=1ll*ret*i%mod;
for(int i=1;i<=m;i++) fm=1ll*fm*i%mod;
return 1ll*ret*fpow(fm,mod-2)%mod;
}
//n!/(n-m)!/m!
int po[N+10][N+10],inv[N+10][N+10];
void init() {
for(int i=1;i<=n;i++) {
po[i][0]=inv[i][0]=1;
inv[i][1]=fpow(po[i][1]=i,mod-2);
for(int j=2;j<=n;j++) po[i][j]=1ll*po[i][j-1]*i%mod,inv[i][j]=1ll*inv[i][j-1]*inv[i][1]%mod;
}
}
int main() {
n=read(),K=read(),mod=read();
init();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
f[i][j]=po[j][i];
for(int t=1;t<=j;t++) {
int A=po[t][i],B=po[t-1][i];
int sum1=(sum[i-1][j-t][t]-sum[i-1][j-t+1][t]+mod)%mod;
int sum2=(sum[i-1][j-t+1][t-1]-sum[i-1][j-t][t-1]+mod)%mod;
Add(f[i][j],(1ll*A*sum1%mod+1ll*B*sum2%mod)%mod);
}
for(int t=1;t<=n;t++) sum[i][j][t]=(sum[i-1][j][t]+1ll*f[i][j]*inv[t][i]%mod)%mod;
}
for(int i=1;i<=n;i++) {
g[i]=f[n][i];
for(int j=1;j<i;j++) Del(g[i],1ll*C(i,j)*g[j]%mod);
}
for(int i=1;i<=n;i++) Add(ans,1ll*C(K,i)*g[i]%mod);
write((ans+mod)%mod),enter;
return WDNMD;
}
C. Brave Seekers of Unicorns
定义一个好序列满足如下条件:
- 序列非空。
- 没有三个连续的元素异或和 \(=0\)。
- 序列递增。
- 序列的值域为 \([1,n]\)。
给定 \(n\),对好序列计数,答案对 \(998244353\) 取模。
Solution
设 \(f_i\) 表示以 \(i\) 结尾的所有方案数,有:
\(f_j\) 可以一波前缀和带走,考虑后面,可以变形为 \(\sum_{k<k\oplus i<i} f_k\)。
因为要考虑 \(i\oplus j\oplus k=0\) 的情况,所以一个位上最多只有两个 \(1\),枚举二进制上 \(i\) 的除最高位的 \(1\) 位 \(d\),钦定 \(j\) 的第 \(d\) 位是 \(1\),\(k\) 的第 \(d\) 位为 \(0\),那么第 \(d\) 位往后都可以随便乱取。
那么 \(k\) 的范围就是 \([2^d,2^{d+1}-1]\),也可以前缀和了。
时间复杂度 \(O(n\log n)\)。
Code
const int N=1e6,mod=998244353;
ll f[N+10],n,sum[N+10];
int main() {
scanf("%d",&n);
f[1]=1;sum[1]=1;
for(int i=2;i<=n;i++) {
f[i]=sum[i-1]+1;
int j;
for(j=20;j>=0;j--)
if(i&(1<<j)) break;
j--;
for(;j>=0;j--) if(i&(1<<j)) f[i]=(f[i]-sum[(1<<(j+1))-1]+sum[(1<<j)-1]+mod)%mod;
sum[i]=(f[i]+sum[i-1])%mod;
}
printf("%lld\n",sum[n]%mod);
}
D. Bank Security Unification
从一个长度为 \(n\) 序列 \(a\) 中取出一个子序列,使得相邻两个元素的按位与值的和最大。
\(2\le n\le 10^6\),\(0\le a_i\le 10^{12}\)。
Solution
我们希望按位与和最大,就是在希望相邻两个元素的二进制位数尽可能相同。
考虑 \(f_i\) 表示末位为 \(i\) 的子序列答案最大是多少,朴素转移是 \(O(n^2)\) 的。
考虑剪枝,如果考虑当前的一位,我们想从哪里转移而来,显然是与之相同的位,如果选这些位靠后一定较靠前的优秀。
所以记录 \(las_{i,0/1}\) 表示第 \(i\) 位上一个是 \(0/1\) 在哪里,每次只从对应的 \(las\) 转移而来。
时间复杂度 \(O(n\log a_i)\)。
Code
const int N=1e6;
int n;
ll f[N+10],g[N+10],ans;
int las[44][2];
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&f[i]);
g[2]=(f[1]&f[2]);
for(int i=1;i<=2;i++)
for(int j=39;j>=0;j--)
if(f[i]&(1ll<<j)) las[j][1]=i;
else las[j][0]=i;
for(int i=3;i<=n;i++) {
for(int j=39;j>=0;j--) {
int zz=(bool)(f[i]&(1ll<<j));
g[i]=max(g[i],g[las[j][zz]]+(f[las[j][zz]]&f[i]));
}
for(int j=39;j>=0;j--)
if(f[i]&(1ll<<j)) las[j][1]=i;
else las[j][0]=i;
}
for(int i=1;i<=n;i++) ans=max(ans,g[i]);
printf("%lld\n",ans);
}
E. Brief Statements Union
给定 \(n\) 个数字,\(k\) 条限制,限制形如 \(a_l\And a_{l+1}\And\ldots\And a_{r}=x\)。
对于某一条限制,如果可以删去这条限制,使得存在满足条件的序列,称这是好的。
输出哪些序列是好的,哪些序列是坏的。
\(1\le n,k\le 10^6\),\(0\le x_i\le 10^{18}\)。
Solution
按位考虑,那么剩下的限制分为两类:\(1\) 限制和 \(0\) 限制。
覆盖掉 \(1\) 限制,考虑 \(0\) 限制的冲突个数:
- 为 \(0\):全都可以。
- 为 \(1\):那个冲突的是内鬼,刀了。
- \(>1\):咋整都不行。
\(0\) 限制的情况就考虑完了。
考虑如何删除 \(1\) 限制,对于每一个只被 \(1\) 限制覆盖一次的点,我们可以删除这个 \(1\) 限制,来满足若干 \(0\) 限制。
对于某一个 \(0\) 限制,我们将能够删除的 \(1\) 限制,使得该 \(0\) 限制合法化的 \(1\) 限制取出,对所有 \(0\) 限制的如此集合取交。
运用差分可以做到 \(O(n\log x_i)\)。
Code
const int N=1e6;
int n,K;
int ql[N+10],qr[N+10];
ll qx[N+10],f[N+10],g[N+10];
int nxt[N+10],pre[N+10],ans[N+10];
int vio[N+10],tot,id,pos[N+10],seg[N+10];
int main() {
scanf("%d %d",&n,&K);
for(int i=1;i<=K;i++) scanf("%d %d %lld",&ql[i],&qr[i],&qx[i]);
nxt[n+1]=n+1,pre[0]=0;
for(int k=0;k<60;k++) {
for(int i=1;i<=n;i++) f[i]=g[i]=0;
for(int i=1;i<=K;i++)
if((1ll<<k)&qx[i])
f[ql[i]]++,f[qr[i]+1]--,
g[ql[i]]+=i,g[qr[i]+1]-=i;
for(int i=1;i<=n;i++) f[i]+=f[i-1],g[i]+=g[i-1];
for(int i=n;i>=1;i--) nxt[i]=f[i]?nxt[i+1]:i;
id=tot=0;
for(int i=1;i<=K;i++)
if(!((1ll<<k)&qx[i]))
if(nxt[ql[i]]>qr[i])
vio[++tot]=i;
if(tot==0) {
for(int i=1;i<=K;i++) ans[i]++;
continue;
}
if(tot==1) ans[vio[1]]++;
for(int i=1;i<=n;i++)
if(f[i]==1&&!pos[g[i]])
seg[pos[g[i]]=++id]=g[i];
for(int i=1;i<=n;i++) pre[i]=(f[i]==1)?i:pre[i-1];
for(int i=n;i>=1;i--) nxt[i]=(f[i]==1)?i:nxt[i+1];
int l=0,r=id;
for(int i=1;i<=tot;i++) {
int u=vio[i];
if(nxt[ql[u]]<=qr[u]) l=max(l,pos[g[nxt[ql[u]]]]);
else l=id+1;
if(pre[qr[u]]>=ql[u]) r=min(r,pos[g[pre[qr[u]]]]);
else r=0;
}
for(int i=l;i<=r;i++) ans[seg[i]]++;
for(int i=1;i<=K;i++) pos[i]=0;
}
for(int i=1;i<=K;i++)
if(ans[i]==60) putchar('1');
else putchar('0');
puts("");
}
F. Border Similarity Undertaking
给定一个字母矩形,求出其有多少的子矩形满足四周都是相同字符。
\(1\le n,m\le 2000\)。
Solution
考虑矩阵分治,即对一个矩阵的长宽进行分治。
对于每一个矩阵进行分治,考虑计算跨过中心线的矩阵个数。
这个过程可以预处理出一个点向上,向下,向左,向右的延伸距离,可以暴力处理。
中心线左右两边分别处理,处理出左右两边能组成的矩形数。
代码细节较多较复杂,时间复杂度 \(O(nm\log n)\)。
Code
const int N=2000;
int n,m;
char ch[N+10][N+10];
int up[N+10][N+10],dn[N+10][N+10],lef[N+10][N+10],rig[N+10][N+10];
int al[N+10][N+10],ar[N+10][N+10];
ll ans;
void solve(int l1,int r1,int l2,int r2) {
if(l1==r1||l2==r2) return;
if(r1-l1+1<=r2-l2+1) {
int mid=(r2+l2)>>1,L,R;
for(int i=l1;i<=r1;i++)
for(int j=l1;j<=r1;j++)
al[i][j]=ar[i][j]=0;
for(int i=l1;i<=r1;i++) {
for(int j=mid;j>=l2&&j>=lef[i][mid];j--)
al[i][min(dn[i][j],r1)]++,
al[i][max(up[i][j],l1)]++;
for(int j=r1-1;j>=i;j--) al[i][j]+=al[i][j+1];
for(int j=l1+1;j<=i;j++) al[i][j]+=al[i][j-1];
for(int j=mid+1;j<=r2&&j<=rig[i][mid+1];j++)
ar[i][min(dn[i][j],r1)]++,
ar[i][max(up[i][j],l1)]++;
for(int j=r1-1;j>=i;j--) ar[i][j]+=ar[i][j+1];
for(int j=l1+1;j<=i;j++) ar[i][j]+=ar[i][j-1];
}
for(int i=l1;i<=r1;i++)
for(int j=i+1;j<=r1;j++) {
if(ch[i][mid]!=ch[j][mid]) continue;
if(ch[i][mid]!=ch[i][mid+1]) continue;
if(ch[j][mid]!=ch[j][mid+1]) continue;
L=lef[i][mid]>=lef[j][mid]?al[i][j]:al[j][i];
R=rig[i][mid+1]<=rig[j][mid+1]?ar[i][j]:ar[j][i];
ans+=1ll*L*R;
}
solve(l1,r1,l2,mid);
solve(l1,r1,mid+1,r2);
}
else {
int mid=(l1+r1)>>1,L,R;
for(int i=l2;i<=r2;i++)
for(int j=l2;j<=r2;j++)
al[i][j]=ar[i][j]=0;
for(int i=l2;i<=r2;i++) {
for(int j=mid;j>=l1&&j>=up[mid][i];j--)
al[i][min(rig[j][i],r2)]++,
al[i][max(lef[j][i],l2)]++;
for(int j=r2-1;j>=i;j--) al[i][j]+=al[i][j+1];
for(int j=l2+1;j<=i;j++) al[i][j]+=al[i][j-1];
for(int j=mid+1;j<=r1&&j<=dn[mid+1][i];j++)
ar[i][min(rig[j][i],r2)]++,
ar[i][max(lef[j][i],l2)]++;
for(int j=r2-1;j>=i;j--) ar[i][j]+=ar[i][j+1];
for(int j=l2+1;j<=i;j++) ar[i][j]+=ar[i][j-1];
}
for(int i=l2;i<=r2;i++)
for(int j=i+1;j<=r2;j++) {
if(ch[mid][i]!=ch[mid][j]) continue;
if(ch[mid][i]!=ch[mid+1][i]) continue;
if(ch[mid][j]!=ch[mid+1][j]) continue;
L=up[mid][i]>=up[mid][j]?al[i][j]:al[j][i];
R=dn[mid][i]<=dn[mid][j]?ar[i][j]:ar[j][i];
ans+=1ll*L*R;
}
solve(l1,mid,l2,r2);
solve(mid+1,r1,l2,r2);
}
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>ch[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
up[i][j]=i>1&&ch[i][j]==ch[i-1][j]?up[i-1][j]:i;
lef[i][j]=j>1&&ch[i][j]==ch[i][j-1]?lef[i][j-1]:j;
}
for(int i=n;i>=1;i--)
for(int j=m;j>=1;j--) {
dn[i][j]=i<n&&ch[i][j]==ch[i+1][j]?dn[i+1][j]:i;
rig[i][j]=j<m&&ch[i][j]==ch[i][j+1]?rig[i][j+1]:j;
}
solve(1,n,1,m);
printf("%lld\n",ans);
}
G. Biological Software Utilities
求出存在完美匹配的 \(n\) 个点的树的数量。\(1\le n\le 10^6\)。
Solution
把两个点绑一块,共有 \(n!!=1\times 3\times \ldots \times (n-1)\) 种方法。
绑一块的点能组成的树,有 \((\frac{n}{2})^{n/2-2}\) 种。
点与点之间相互连接,有 \(4^{n/2-1}\) 种。
乘起来,没了,时间复杂度 \(O(n)\)。
Code
const int N=1e6,mod=998244353;
int n;
int ans=1;
int fpow(int x,int y) {
if(y<0) return 1;
int ret=1;
for(;y;y>>=1) {
if(y&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
}
return ret;
}
int main() {
scanf("%d",&n);
if(n%2) {puts("0");return 0;}
for(int i=1;i<=n;i+=2) ans=1ll*ans*i%mod;
ans=1ll*ans*fpow(n/2,n/2-2)%mod*fpow(4,n/2-1)%mod;
printf("%d\n",ans);
}
H. Bytelandia States Union
从 \((x_1,y_1)\) 走到 \((x_2,y_2)\),求最短路径。
其中从 \((x,y)\) 向四个方向走的方案不一,具体看原题。
\(1\le T\le 5\times 10^4\),\(1\le x_1,y_1,x_2,y_2\le 10^9\)。
Solution
诈骗题,我可以很负责任的告诉你,一条路径 \((x_1,y_1),(x_2,y_2),(x_3,y_3),\ldots,(x_k,y_k)\) 的费用是 \(x^2_ky^2_k-x^2_1y^2_1-x_k^2-x_k^2+\sum^{k}_{i=1}x^2_i+y^2_i\),证明?可以问hht2005,总之我不会。
最小化路径,只需最小化后面的值,所以只要尽可能挨近 \(x=y\) 这条直线就可以了。
大力分讨即可,单次时间复杂度 \(O(1)\)。
Code
void solve() {
int x1,y1,x2,y2;
scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2);
ans=(p2(1ll*x2*y2%mod)-p2(1ll*x1*y1%mod)+mod)%mod;
ans=(ans-p2(x2)-p2(y2)+mod)%mod;
if(x1>x2) swap(x1,x2),swap(y1,y2);
if(y2>y1)
if(x1<y1) {
int nx=min(y1,x2);
Add(ans,1ll*(nx-x1)*p2(y1)%mod);
Add(ans,sum(x1,nx-1));
if(nx==x2) {
Add(ans,1ll*(y2-y1+1)*p2(x2)%mod);
Add(ans,sum(y1,y2));
}
else {
int nxt=min(x2,y2);
Add(ans,(sum(nx,nxt-1)*3ll%mod+sum(nx+1,nxt))%mod);
if(nxt==y2) {
Add(ans,1ll*p2(y2)*(x2-nxt+1)%mod);
Add(ans,sum(nxt,x2));
}
else {
Add(ans,1ll*p2(x2)*(y2-nxt+1)%mod);
Add(ans,sum(nxt,y2));
}
}
}
else {
int ny=min(x1,y2);
Add(ans,1ll*(ny-y1)*p2(x1)%mod);
Add(ans,sum(y1,ny-1));
if(ny==y2) {
Add(ans,1ll*(x2-x1+1)*p2(y2)%mod);
Add(ans,sum(x1,x2));
}
else {
int nxt=min(x2,y2);
Add(ans,sum(ny,nxt-1)*2ll%mod);
Add(ans,sum(ny,nxt-1)+sum(ny+1,nxt));
if(nxt==x2) {
Add(ans,1ll*p2(x2)*(y2-nxt+1)%mod);
Add(ans,sum(nxt,y2));
}
else {
Add(ans,1ll*p2(y2)*(x2-nxt+1)%mod);
Add(ans,sum(nxt,x2));
}
}
}
else {
Add(ans,1ll*(y1-y2)*p2(x1)%mod);
Add(ans,sum(y2+1,y1));
Add(ans,1ll*(x2-x1+1)*p2(y2)%mod);
Add(ans,sum(x1,x2));
}
printf("%lld\n",(ans%mod+mod)%mod);
}
signed main() {
ll t;scanf("%lld",&t);
while(t--) solve();
}
I. Binary Supersonic Utahraptors
Alice 和 Bob 又在玩游戏,Alice 有黑与白两种物品,Bob 也有黑与白两种物品。
有 \(k\) 个回合,每一回合中,Alice 先送给 Bob \(s_i\) 个物品,Bob 则回礼 \(s_i\) 个物品。
Alice 想让 Alice 的黑物品与 Bob 的白物品差尽可能小,Bob 则想让上述东西最大。
求最后的这个值。
\(1\le n,m,k\le 3\times 10^5\)。
Solution
人定不胜天,Alice 和 Bob 并不能决定这一切。
答案恒定不变,是总的黑物品数与 \(n\) 的差。
时间复杂度 \(O(1)\)。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,k,r,y;
int main() {
scanf("%d %d %d",&n,&m,&k);
for(int i=1,x;i<=n;i++) {
scanf("%d",&x);
if(x==1) r++;
}
for(int i=1,x;i<=m;i++) {
scanf("%d",&x);
if(x==1) r++;
}
printf("%d\n",abs(n-r));
}
J. Burnished Security Updates
求一张图的最小的覆盖所有边的独立集,\(2\le n\le 3\times 10^5\),\(1\le m\le 3\times 10^5\)。
Solution
要覆盖所有边,所以相邻的两个点一定取得状态不同。
考虑二分图染色,如果原图有奇环,则一定无解,否则取较小的那种颜色的点集。
时间复杂度 \(O(n+m)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=3e5;
int n,m,vis[N+10],cnt[4],ans;
vector<int> G[N+10];
bool dfs(int u,int col) {
vis[u]=col;cnt[col]++;
bool fl=1;
for(auto v:G[u])
if(!vis[v]) fl&=dfs(v,3-col);
else if(vis[v]==col) return 0;
return 1;
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1,x,y;i<=m;i++) {
scanf("%d %d",&x,&y);
G[x].pb(y),G[y].pb(x);
}
for(int i=1;i<=n;i++)
if(!vis[i]) {
cnt[1]=cnt[2]=0;
if(!dfs(i,1)) {
puts("-1");
return 0;
}
ans+=min(cnt[1],cnt[2]);
}
printf("%d\n",ans);
}
K. Bookcase Solidity United
有 \(n\) 块木板,第 \(i\) 块木板有稳定性 \(a_i\),木板从高到低排列。
在木板上放置铱球,第 \(i\) 块木板被放了大于等于 \(a_i\) 个铱球后会碎裂,有 \(\lfloor\frac k 2\rfloor\) 会下坠到下一块木板上。
求至少需要多少铱球才可以砸碎前 \(i\) 块木板。
\(1\le n\le 70\),\(1\le a_i\le 150\)。
Solution
设 \(f_{l,r,k}\) 表示砸碎 \([l,r]\) 的木板,留下 \(k\) 个球的最小球数。
有一个显然的转移:\(f_{l,r,k}+\max(a_{r+1}-k,0)\to f_{l,r+1,\max(k,a_{r+1})/2}\),表示继承下若干个球来砸下面的。
同时,我们考虑分段砸,\(f_{l,r,k_1+k_2}=f_{l,mid,k_1}+f_{mid+1,r,k_2}\)。
直接暴力转移是 \(O(n^3m^2)\) 的。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=200,M=200;
int n,a[N+10],m;
int f[N+10][N+10][M+10];
void chkmin(int &x,int y) {
if(x>y) x=y;
}
int main() {
scanf("%d",&n);
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][i][a[i]/2]=a[i],m=max(m,a[i]);
for(int len=2;len<=n;len++)
for(int l=1;l+len-1<=n;l++) {
int r=l+len-1;
for(int i=0;i<=m;i++) chkmin(f[l][r][max(i,a[r])/2],f[l][r-1][i]+max(0,a[r]-i));
for(int i=0;i<=m;i++)
for(int j=l;j<r;j++)
for(int k=0;k<=i;k++)
chkmin(f[l][r][i],f[l][j][k]+f[j+1][r][i-k]);
}
for(int i=1;i<=n;i++) {
int ans=INT_MAX;
for(int j=0;j<=m;j++) ans=min(ans,f[1][i][j]);
printf("%d ",ans);
}
puts("");
return 0;
}
M. Brilliant Sequence of Umbrellas
构造一个长度至少为 \(\lceil \frac2 3 \sqrt{n}\rceil\) 的值域为 \([1,n]\) 递增序列,使得两两之间的 \(\gcd\) 也递增。
\(1\le n\le 10^{12}\)。
Solution
观察样例,考虑构造一个每隔两个数均互质的序列,然后将这个序列相邻两项的积作为原序列。
这样一定满足 \(\gcd\) 递增。
Code
typedef long long ll;
const int N=1e6;
ll n,a[N+10],cnt,b[N+10];
int main() {
scanf("%lld",&n);
b[++cnt]=1,b[++cnt]=1;
int lim=ceil(2.0*sqrt(n)/3);
for(ll i=2;cnt<=lim+1&&b[cnt]*i<=n;i++)
if(__gcd(b[cnt-1],i)==1)
b[++cnt]=i;
printf("%lld\n",cnt-1);
for(int i=1;i<cnt;i++) printf("%lld ",b[i]*b[i+1]);
puts("");
}
N. Best Solution Unknown
有 \(n\) 个人打比赛,每个人有个实力值,实力值大的会打败实力值小的,相等的都有机会赢。
打赢一个人可以增加 \(1\) 的实力值,每个人只能和相邻的人打。
每次随机取比赛,求会有哪些人可能赢。
\(1\le n\le 10^6\),\(1\le a_i\le 10^9\)。
Solution
考虑对于一个大段,一个人能不能赢,抽取出最大值,发现最大值分成两份,一份是左一份是右,要想赢得翻过最大值这座大山。
那么我们就可以分治了,找出最大值来分治,每次记录左边或右边要至少多少才可以赢。
分治的时候用 ST 表加速求最值就可以了。
Code
const int N=1e6;
int a[N+10],n,Log[N+10],Max[N+10][30],id[N+10][30];
bool nb[N+10];
int query(int l,int r) {
int k=Log[r-l+1];
int ret=max(Max[l][k],Max[r-(1<<k)+1][k]);
if(ret==Max[l][k]) return id[l][k];
else return id[r-(1<<k)+1][k];
}
void dfs(int l,int r,int c) {
if(l>r) return;
if(l==r) {
if(a[l]>=c) nb[l]=1;
return;
}
int id=query(l,r);
if(a[id]<c) return;
nb[id]=1;
dfs(l,id-1,max(c,a[id]-id+1+l));
dfs(id+1,r,max(c,a[id]+id+1-r));
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
Log[1]=0;
for(int i=2;i<=n;i++) Log[i]=Log[i/2]+1;
for(int i=1;i<=n;i++) Max[i][0]=a[i],id[i][0]=i;
for(int j=1;j<=20;j++)
for(int i=1;i+(1<<(j-1))<=n;i++) {
Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
if(Max[i][j]==Max[i][j-1]) id[i][j]=id[i][j-1];
else id[i][j]=id[i+(1<<(j-1))][j-1];
}
dfs(1,n,0);
int cnt=0;
for(int i=1;i<=n;i++) if(nb[i]) cnt++;
printf("%d\n",cnt);
for(int i=1;i<=n;i++) if(nb[i]) printf("%d ",i);
puts("");
}