2022 年 ? 月训练记录
Souvenirs / 面试的考验 *3100
Source: Codeforces Round #397 / JSOI2009
Solution
离线,考虑对于每个 \(r\),维护 \(f_{i,r}\) 表示 \([i,r]\) 的答案,暴力更新仍然没有任何复杂度改观。
改单点询问 \(f_{l,r}\) 为区间询问 \(i\in [l,r]\),由这点可以考虑使用线段树维护 \(f_{l,r}\)。
\(r\) 向右移的同时考虑计算 \(a_r\) 到 \([1,r-1]\) 的贡献,发现这个计算也十分 hard,复杂度依旧不改观。
考虑剪枝,不难发现如果存在 \(i<j\),使得 \(ans_{i,r}>ans_{j,r}\) 就可以不往左边继续更新了。
发现这玩意复杂度是对的,因为这种向左边的次数其实只有 \(\log V\) 次。
那么,本题就在 \(O(n\log n\log V)\) 的时间复杂度内解决了。
Code
const int N=1e5,inf=0x3f3f3f3f;
int n,a[N+10];
vi val[N*4+10];
int ans[N*4+10],mi;
void build(int p,int l,int r) {
FOR(i,l,r) val[p].pb(a[i]);
sort(begin(val[p]),end(val[p]));
ans[p]=inf;
if(l==r) return;
int mid=(l+r)>>1;
build(p*2,l,mid),build(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int x,int v) {
// printf("What are you change %d,%d,%d,%d,%d\n",p,l,r,x,v);
if(r<=x) {
auto it=upper_bound(begin(val[p]),end(val[p]),v);
// if(it!=end(val[p])) cerr<<*it<<endl;
if(it!=end(val[p])) ans[p]=min(ans[p],*it-v);
if(it!=begin(val[p])) ans[p]=min(ans[p],v-*prev(it));
if(mi<=ans[p]) return;
if(l==r) {
mi=min(ans[p],mi);
// printf("ans[%d] is %d %d\n",p,ans[p],mi);
return;
}
}
int mid=(l+r)>>1;
if(x>mid) change(p*2+1,mid+1,r,x,v);
change(p*2,l,mid,x,v);
ans[p]=min(ans[p],min(ans[p*2+1],ans[p*2]));
mi=min(mi,ans[p]);
// printf("ans[%d] is %d %d\n",p,ans[p],mi);
}
int query(int p,int l,int r,int x,int y) {
// printf("What are you query %d,%d,%d,%d,%d\n",p,l,r,x,y);
if(x<=l&&r<=y) return ans[p];
int mid=(l+r)>>1,ret=inf;
if(x<=mid) ret=min(ret,query(p*2,l,mid,x,y));
if(y>mid) ret=min(ret,query(p*2+1,mid+1,r,x,y));
return ret;
}
struct QUE {
int l,r,id;
} Q[N*4+10];
bool cmp(QUE a,QUE b) {
return a.r<b.r;
}
int m,out[N*4+10];
int main() {
scanf("%d",&n);
FOR(i,1,n) scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&m);
FOR(i,1,m) {
scanf("%d %d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+m+1,cmp);
int tmp=1;
FOR(i,2,n) {
mi=inf;change(1,1,n,i-1,a[i]);
// printf("change %d end min is %d\n",i,mi);
for(;tmp<=m&&Q[tmp].r<=i;tmp++) out[Q[tmp].id]=query(1,1,n,Q[tmp].l,i);
}
FOR(i,1,m) printf("%d\n",out[i]);
}
Anticube *2608
Source: AtCoder Grand Contest 003
Solution
显然可以大力质因数分解然后贺个 Pollard-Rho 直接碾过,但是这显然不是正解。
设剔除 \(a_i\) 的立方因子小于 \(\sqrt[3]{V}\) 的因子后的数是 \(x\),剔除 \(a_i\) 在 \(\sqrt[3]{V}\) 内的所有因子的值是 \(v\),先特判 \(x=1\) 的情况,这说明其为立方数,可以去掉,按照 \(v\) 分讨:
- \(v=1\) 时,说明这个数只有 \([1,\sqrt[3]{V}]\) 的因数,直接把 \(x\) 放入需要的容器里。
- \(v=p\) 时,其中 \(p\in \text{prime}\) 且 \(p\in[\sqrt[3]{V},\sqrt{V}]\),那么 \(px\) 就是对应的数。
- \(v=p^2\) 时,其中 \(p\in \text{prime}\) 且 \(p\in[\sqrt[3]{V},\sqrt{V}]\),那么 \(p^2x\) 就是对应的数。
- \(v=p\),\(p\in \text{prime}\) 且 \(p\in[\sqrt{V},V]\) 或者 \(v=pq\),\(p,q\in \text{prime}\) 且 \(p,q\in[\sqrt[3]{V},\sqrt{V}]\) 时,这个数没有对应的数,可以直接累加进答案。
时间复杂度 \(O(n\sqrt[3]{V})\)。
Code
const int N=1e5,sq3=2250;
int n,ans,in;
ll a[N+10];
int pr[sq3+10],pri[N+10][20],x[N+10][20],cnt[N+10],X[N+10];
bool vis[sq3+10],fl[N+10];
map<int,int> mp;
int calc(int id) {
// cerr<<"calc"<<" "<<id<<endl;
int ret=1;
if((int)sqrt(a[id])*(int)sqrt(a[id])==a[id]) ret*=sqrt(a[id]);
else ret*=a[id]*a[id];
FOR(i,1,cnt[id])
if(pri[id][i]==2) ret*=x[id][i];
else ret*=x[id][i]*x[id][i];
return ret;
}
signed main() {
scanf("%lld",&n);
FOR(i,1,n) scanf("%lld",&a[i]);
FOR(i,2,sq3) {
if(!vis[i]) pr[++pr[0]]=i;
for(int j=1;j<=pr[0]&&i*pr[j]<=sq3;j++) {
vis[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}
// bool fl=0;
// cerr<<"WDNMD"<<endl;
FOR(i,1,n) {
x[i][0]=1;
FOR(j,1,pr[0]) if(a[i]%pr[j]==0) {
x[i][++cnt[i]]=pr[j];
while(a[i]%pr[j]==0) a[i]/=pr[j],pri[i][cnt[i]]++;
pri[i][cnt[i]]%=3;
if(!pri[i][cnt[i]]) cnt[i]--;
else x[i][0]*=pow(pr[j],pri[i][cnt[i]]);
}
if(!cnt[i]&&a[i]==1) ans=1;
}
FOR(i,1,n) {
if(!cnt[i]&&a[i]==1) continue;
if((int)sqrt(a[i])*(int)sqrt(a[i])==a[i]) X[++in]=i,mp[a[i]*x[i][0]]++;
else if(a[i]>N) ans++;
else X[++in]=i,mp[a[i]*x[i][0]]++;
}
// cerr<<"WDNMD"<<endl;
// if(fl) ans++;
// cerr<<ans<<endl;
FOR(I,1,in) {
int i=X[I];
int dy=calc(i);
if(mp.find(dy)==mp.end()) ans+=mp[x[i][0]*a[i]],mp[x[i][0]*a[i]]=0;
else ans+=max(mp[x[i][0]*a[i]],mp[dy]),mp[x[i][0]*a[i]]=0,mp[dy]=0;
}
printf("%lld\n",ans);
}
Sequential operations on Sequence *2983
Source: AtCoder Grand Contest 003
Solution
一次较小的操作可以盖去所有大操作,所以考虑先对 \(a_i\) 数组做一个单调栈,使序列单调上升。
正难则反,考虑从后往前推,对一次从小到大的操作进行分拆,可以变化为一段前缀重复若干次加上一块边角料。
考虑如何处理这个边角料,我们可以找到最大的 \(\le\) 边脚料长度的操作,此时我们可以将边角料分拆成一段前缀重复若干次加上另一块边角料,这个可以继续递归。
考虑维护每个数的操作步数,发现上述分拆以及从后往前推的边界恰恰对应一次区间加,又因为最后统一查,可以利用差分做到线性。
时间复杂度 \(O(n\log^2 n)\)。
Code
const int N=1e5;
int n,top;
ll a[N+10],f[N+10],ans[N+10];
void solve(ll L,ll d) {
int x=upper_bound(a+1,a+top+1,L)-a-1;
if(x==0) return ans[1]+=d,ans[L+1]-=d,void();
f[x]+=L/a[x]*d,solve(L%a[x],d);
}
int main() {
int q;
scanf("%d %d",&n,&q),a[++top]=n;
FOR(i,1,q) {
ll x;scanf("%lld",&x);
while(top&&a[top]>=x) top--;
a[++top]=x;
}
f[top]=1;
ROF(i,top-1,1) f[i]+=a[i+1]/a[i]*f[i+1],solve(a[i+1]%a[i],f[i+1]);
ans[1]+=f[1],ans[a[1]+1]-=f[1];
FOR(i,1,n) ans[i]+=ans[i-1],printf("%lld\n",ans[i]);
}
Fraction of Fractal *3344
Source: AtCoder Grand Contest 003
Solution
根据连通块是否可以上下左右拼接分讨,设黑点个数为 \(x\):
- 可以上下拼接,也可以左右拼接,答案为 \(1\)。
- 不可以上下拼接,也不可以左右拼接,答案为 \(x^{K-1}\)。
- 上下或者左右可以拼接。
仅需要关注后一种情况,不失一般性的,考虑左右拼接的情况。
我们要求连通块个数,此时只有左右可以拼接,记 \(b_i\) 为 \(i\) 级分形的黑点个数,\(a_i\) 为 \(i\) 级分形中左右均为黑点的对数,\(b_i=b_{i-1}b_1\) 是显然的,但是 \(a_i\) 并不好转移,不妨新记一个 \(c_i\) 表示 \(i\) 级分形中边界上会连起来的对数。
有转移:\(c_i=c_{i-1}c_1\),\(a_i=a_{i-1}c_{i-1}+b_{i-1}a_1\)。
转移可以写成矩阵形式,时间复杂度 \(O(\log K)\)。
Code
const int N=1000,mod=1e9+7;
int n,m;
ll K;
char mp[N+10][N+10];
int fpow(int x,ll y) {
int ret=1;
for(;y;y>>=1) {
if(y&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
}
return ret;
}
struct matrix {
ll a[2][2];
matrix() {memset(a,0,sizeof a);}
matrix operator *(const matrix &b) {
matrix ret;
FOR(i,0,1) FOR(j,0,1) FOR(k,0,1)
ret.a[i][j]+=a[i][k]*b.a[k][j];
FOR(i,0,1) FOR(j,0,1) ret.a[i][j]%=mod;
return ret;
}
} tr;
int main() {
scanf("%d %d %lld",&n,&m,&K);
if(!K) return puts("1"),0;
int cnt=0;
FOR(i,1,n) FOR(j,1,m) cin>>mp[i][j];
FOR(i,1,n) FOR(j,1,m) if(mp[i][j]=='#') cnt++;
int lef=0,up=0;
FOR(i,1,n) if(mp[i][1]=='#'&&mp[i][m]=='#') lef++;
FOR(i,1,m) if(mp[1][i]=='#'&&mp[n][i]=='#') up++;
if(!up&&!lef) return printf("%d\n",fpow(cnt,K-1)),0;
if(up&&lef) return puts("1"),0;
K--;
int rig=0,dn=0;
FOR(i,1,n) FOR(j,1,m-1) if(mp[i][j]=='#'&&mp[i][j+1]=='#') rig++;
FOR(i,1,n-1) FOR(j,1,m) if(mp[i][j]=='#'&&mp[i+1][j]=='#') dn++;
// cerr<<lef<<" "<<up<<" "<<rig<<" "<<dn<<endl;
if(up) tr.a[0][0]=cnt,tr.a[0][1]=dn,tr.a[1][1]=up;
else tr.a[0][0]=cnt,tr.a[0][1]=rig,tr.a[1][1]=lef;
matrix res;res.a[0][0]=res.a[1][1]=1;
for(;K;K>>=1) {
if(K&1) res=res*tr;
tr=tr*tr;
}
printf("%lld\n",(0ll+res.a[0][0]-res.a[0][1]+mod)%mod);
}

浙公网安备 33010602011771号