冲刺国赛模拟 27

感觉今天 T2 十分的迷惑。复杂度多个 \(\log\) 和多个 \(n\) 同分。正解被卡常。SAM 写挂能过所有样例。所以 T2 挂了,接着掉分。

地图编辑

先判无解。首先 \(d\) 范围不在把所有障碍删掉的最短路和原图最短路之间无解。然后手模可以发现所有路径长度奇偶性相同,因此奇偶性不同无解。

剩下的都有解。因为任意局面一定能删一个块使得最短路长度最多 \(-2\)。然后题解的结论是一个块只要删了仍然合法且两边不都是墙则删了一定更优。然后在这种情况下如果删掉不合法则最短路长度不会变,那么直接扫一遍所有连通块 dfs 出所有的格子,然后二分删一个后缀即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int dx[]={1,0,0,-1},dy[]={0,1,-1,0};
int n,m,d,dis[1010][1010];
bool vis[1010][1010];
char s[1010][1010];
pair<int,int>S,T;
queue<pair<int,int> >q;
bool check1(int x,int y){
    return x>=1&&x<=n&&y>=1&&y<=m&&s[x][y]!='#'&&dis[x][y]==-1;
}
void bfs(){
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)dis[i][j]=-1;
    q.push(S);dis[S.first][S.second]=0;
    while(!q.empty()){
        pair<int,int> x=q.front();q.pop();
        for(int i=0;i<4;i++){
            if(check1(x.first+dx[i],x.second+dy[i])){
                dis[x.first+dx[i]][x.second+dy[i]]=dis[x.first][x.second]+1;
                q.push({x.first+dx[i],x.second+dy[i]});
            }
        }
    }
}
bool check(int x,int y){
    return x>1&&x<n&&y>1&&y<m&&!vis[x][y]&&s[x][y]=='#';
}
vector<pair<int,int> >bl;
void dfs(int x,int y){
    vis[x][y]=true;bl.push_back(make_pair(x,y));
    for(int i=0;i<4;i++){
        if(check(x+dx[i],y+dy[i]))dfs(x+dx[i],y+dy[i]);
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&d);
    for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
    
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
        dis[i][j]=-1;
        if(s[i][j]=='S')S=make_pair(i,j);
        if(s[i][j]=='F')T=make_pair(i,j);
    }
    int val=abs(S.first-T.first)+abs(S.second-T.second);
    if(d<val||(d&1)!=(val&1)){
        puts("No");return 0;
    }
    bfs();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(dis[i][j]==-1&&s[i][j]!='#'){
                puts("nsdd");return 0;
            }
        }
    }
    if(dis[T.first][T.second]<d){
        puts("No");return 0;
    }
    puts("Yes");
    for(int i=2;i<n;i++){
        if(check(i,2))dfs(i,2);
        if(check(i,m-1))dfs(i,m-1);
    }
    for(int i=2;i<m;i++){
        if(check(2,i))dfs(2,i);
        if(check(n-1,i))dfs(n-1,i);
    }
    for(int i=2;i<n;i++)for(int j=2;j<m;j++)if(check(i,j))dfs(i,j);
    int l=0,r=bl.size();
    while(l<r){
        int mid=(l+r)>>1;
        for(int i=0;i<mid;i++)s[bl[i].first][bl[i].second]='#';
        for(int i=mid;i<bl.size();i++)s[bl[i].first][bl[i].second]='.';
        bfs();
        if(dis[T.first][T.second]>=d)r=mid;
        else l=mid+1;
    }
    for(int i=0;i<l;i++)s[bl[i].first][bl[i].second]='#';
    for(int i=l;i<bl.size();i++)s[bl[i].first][bl[i].second]='.';
    for(int i=1;i<=n;i++)printf("%s\n",s[i]+1);
    return 0;
}

摸底测试

我以为我秒了。结果赛后过题。典。

首先我搬过一个类似的题。先假装我们可以速算 \(g(s)\),那二分一个最大值,然后里边的 \(s\) 显然是贪心往右划。直接二分复杂度不太对,先倍增到一个长度区间再二分复杂度就对了。这部分是两只 \(\log\)

然后考虑速算 \(g(s)\)。考虑每个本质不同子串在哪些划分位置有贡献,它是从第一个 endpos 到最后一个 endpos 减长度的位置这一段连续段都有贡献。那么建出 SAM,每个节点的贡献就是一个区间加常数加上一个区间加等差数列。如果写线段树或者树状数组的话多个 \(\log\),实际上直接差分就能线性了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,k,ans,f[50010],dfn[100010],cnt[100010];
char s[50010];
struct SAM{
	int cnt,last,len[100010],fa[100010],trie[100010][26],mx[100010],mn[100010];
	void clear(){
		for(int i=1;i<=cnt;++i){
            len[i]=fa[i]=mx[i]=0,mn[i]=0x3f3f3f3f;
            for(int j=0;j<26;j++)trie[i][j]=0;
        }
		last=cnt=1;
	}
	void ins(int ch,int id){
		int p=last;last=++cnt;
		len[last]=len[p]+1;mx[last]=mn[last]=id;
		while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
		if(!p){
			fa[cnt]=1;return;
		}
		int q=trie[p][ch];
		if(len[p]+1==len[q]){
			fa[cnt]=q;return;
		}
		len[++cnt]=len[p]+1;
		for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
		fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
		while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
	}
}sam;
long long val[50010],d[50010];
void update(int l,int r,int k,int D=0){
	if(l>r||l<=0||r<=0)return;
	if(D==-1)k=r-l+1;
	val[l]+=k;
	if(!D)val[r+1]-=k;
	else d[l+1]+=D,d[r+2]-=D;
}
long long get(int l,int r){
	for(int i=l;i<=r;i++)sam.ins(s[i]-'a',i-l+1);
	for(int i=0;i<=r-l+1;i++)cnt[i]=val[i]=d[i]=0;
	for(int i=1;i<=sam.cnt;i++)cnt[sam.len[i]]++;
	for(int i=1;i<=r-l+1;i++)cnt[i]+=cnt[i-1];
	for(int i=1;i<=sam.cnt;i++)dfn[cnt[sam.len[i]]--]=i;
	for(int i=sam.cnt;i>=2;i--){
        int x=dfn[i];
        sam.mx[sam.fa[x]]=max(sam.mx[sam.fa[x]],sam.mx[x]);
        sam.mn[sam.fa[x]]=min(sam.mn[sam.fa[x]],sam.mn[x]);
    }
    for(int x=2;x<=sam.cnt;x++){
        update(sam.mn[x],sam.mx[x]-sam.len[x]-1,sam.len[x]-sam.len[sam.fa[x]]);
        update(max(sam.mx[x]-sam.len[x],sam.mn[x]),sam.mx[x]-sam.len[sam.fa[x]]-1,0,-1);
    }
	long long ans=0;
    for(int i=1;i<=r-l;i++){
        d[i]+=d[i-1];
		val[i]+=val[i-1]+d[i];
		ans+=val[i]*val[i];
    }
	sam.clear();
	return ans;
}
bool check(long long x){
	int l,r,i,cnt,len;
	for(i=1,cnt=0;i<=n&&cnt<k;i=l+1,cnt++){
		for(len=1;i+len-1<=n;len<<=1){
			if(get(i,i+len-1)>x)break;
		}
		if(len==1)return false;
		l=i+(len>>1)-1,r=min(i+len-1,n);
		while(l<r){
			int mid=(l+r+1)>>1;
			if(get(i,mid)<=x)l=mid;
			else r=mid-1;
		}
	}
	return i>n;
}
signed main(){
	scanf("%d%d%s",&n,&k,s+1);
    sam.clear();memset(sam.mn,0x3f,sizeof(sam.mn));
	long long l=0,r=get(1,n);
	while(l<r){
		long long mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	printf("%lld\n",l);
	return 0;
}

回文之和

论文题。

结论:任意进制 \(\ge 5\) 的正整数可以被写成三个回文数的和。证明是构造性的,然而看上去情况就很多而且很复杂,我也没细看。或许之后会翻译一下。

首先一个数的情况是平凡的。三个数的情况可以枚举第一个变成两个的情况。题解说这个枚举的数不会很大。

两个的情况,设 \(X\) 的位数是 \(D\),则大的一个的位数只可能是 \(D\)\(D-1\),那么枚举小的一个的位数。假设位数不等,那么大的一个的最高位只有进位 \(1\) 或不进位两种情况,那么根据回文可以一步一步推回来,然后每推一位枚举是否进位,复杂度 \(O(D2^{\frac D2})\)。优化是当差的位数比较多的时候可以一次推若干位。

位数相等的时候看起来要枚举最高位是什么数字,但是发现它是不影响的,仍然只有是否进位两种情况,随便填一个数上去就行了。

代码巨大难写。摆了。

posted @ 2023-06-30 07:47  gtm1514  阅读(16)  评论(1编辑  收藏  举报