退役选手为了维持一下手感,从必刷题和金考卷的海洋的跳出来刷点题
感谢symbol一套题把我的水准由普及给拉上来了
比赛过程
早上八点挣扎着从床上起来看题
感觉T1比较像我之前搬过的一道原题,应该是(bfs)加上一些优化,感觉码量可能会比较大就先丢了
T2的限制条件和一年前写过的某场CF的D题是一样的,就是求的不大一样,刚起来不大想推式子就又丢了
T3应该是个比较奇妙的性质题,一眼会(O(n^2)),再要优化的话应该是要优化询问区间和(check)方式,不太想想就丢了
爆零预定
最终决定顺序开题,T1感觉就是一个最短路,8:25开始码,磕磕碰碰码了半个小时写完了,随意debug了一下就放在一边了
出去吃了个早饭(顺便%了一下准高三大佬hokiplus),路上大致发现T3只需要(checkO(n))个区间就可以了,但是还是只会(O(n^2))的(check)
回来之后开(T2),期间有个地方想错了卡了一会,其它都还好,码完+调完10:30了
又来看T3,先花10min写了个(60pts),后来感觉会满分了,被一个奥妙重重的边界卡了一下,(11:20)写完了
jzoj这回卡了48面评测
吃完饭回来一看T3(fst)了,发现把(sum[r])写成了(sum[r+1]),改完就A了,十分难受
还是要提高代码能力啊
Solution
T1
直接bfs不知道多少分啊(我可能还不会直接bfs)
考虑最短路,首先四联通的点连边,接下来考虑传送门的影响:可以使某一个点到达上下左右最近的墙,花费是到达距离这个点最近的墙的时间
建完图直接最短路就好了
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 1061109567
#define eps 1e-8
const int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
struct node{int to,nxt,cost;}sq[2002000];
int all=0,head[250010];
struct hnode{int u,dis;};
bool operator <(hnode p,hnode q){return p.dis>q.dis;}
priority_queue<hnode> hp;
int n,m,L[510][510],R[510][510],U[510][510],D[510][510],mind[510][510];
int id[510][510],dis[250010],st,ed,tot=0;
bool vis[250010];
char s[510][510];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void bfs()
{
queue<pii> q;
rep(i,1,n) rep(j,1,m)
if (s[i][j]=='#') q.push(mp(i,j));
while (!q.empty())
{
int x=q.front().fir,y=q.front().sec;q.pop();
rep(i,0,3)
{
int nx=x+dx[i],ny=y+dy[i];
if ((nx>1) && (nx<=n) && (ny>1) && (ny<=m) && (s[nx][ny]=='.') && (!mind[nx][ny]))
{
mind[nx][ny]=mind[x][y]+1;q.push(mp(nx,ny));
}
}
}
rep(i,1,n) rep(j,1,m)
if (s[i][j]=='.')
{
if (s[i][j-1]=='#') L[i][j]=id[i][j];
else L[i][j]=L[i][j-1];
if (s[i-1][j]=='#') U[i][j]=id[i][j];
else U[i][j]=U[i-1][j];
}
per(i,n,1) per(j,m,1)
if (s[i][j]=='.')
{
if (s[i][j+1]=='#') R[i][j]=id[i][j];
else R[i][j]=R[i][j+1];
if (s[i+1][j]=='#') D[i][j]=id[i][j];
else D[i][j]=D[i+1][j];
}
}
void add(int u,int v,int w)
{
all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all;
}
void build()
{
rep(i,1,n) rep(j,1,m)
{
if (s[i][j]=='.')
{
rep(k,0,3)
{
int nx=i+dx[k],ny=j+dy[k];
if (s[nx][ny]=='.') add(id[i][j],id[nx][ny],1);
}
}
if (L[i][j]!=id[i][j]) add(id[i][j],L[i][j],mind[i][j]);
if (R[i][j]!=id[i][j]) add(id[i][j],R[i][j],mind[i][j]);
if (U[i][j]!=id[i][j]) add(id[i][j],U[i][j],mind[i][j]);
if (D[i][j]!=id[i][j]) add(id[i][j],D[i][j],mind[i][j]);
}
}
void dij()
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
hp.push((hnode){st,0});dis[st]=0;
while (!hp.empty())
{
int u=hp.top().u;hp.pop();
if (vis[u]) continue;vis[u]=1;
for (int i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if (dis[v]>dis[u]+sq[i].cost)
{
dis[v]=dis[u]+sq[i].cost;
if (!vis[v]) hp.push((hnode){v,dis[v]});
}
}
}
}
int main()
{
freopen("cell.in","r",stdin);
freopen("cell.out","w",stdout);
n=read();m=read();
rep(i,1,n) scanf("%s",s[i]+1);
rep(i,1,n) rep(j,1,m) id[i][j]=(++tot);
rep(i,1,n) rep(j,1,m)
if (s[i][j]=='C') {st=id[i][j];s[i][j]='.';}
else if (s[i][j]=='F') {ed=id[i][j];s[i][j]='.';}
bfs();build();dij();
if (dis[ed]<maxd) printf("%d",dis[ed]);
else puts("no");
return 0;
}
所以T1的码量可能是T2+T3是什么鬼
T2
如果你写过这个题的话T2就比较简单了
根据BST的性质肯定是要按照(key)值排个序的,预处理一下任意两个点之间是否能连边
接下来考虑(dp),暴力dp的话就应该是记(dp_{i,j,k})表示把区间([i,j])建成以(k)为根的(BST)时的答案,这样(dp)好像是(O(n^5))的不大行,
注意到一棵(BST)的左右子树是互相独立的,于是可以记(f_{i,j,0})表示区间([i,j])将会作为(i-1)的右子树的答案,(f_{i,j,1})表示([i,j])作为(j+1)的左子树的答案,转移的话枚举(i-1)或者(j+1)的直系儿子是哪一个就可以了,时间是(O(n^3))的
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
#define int long long
typedef long long ll;
typedef long double db;
typedef pair<int,int> pii;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define ky first
#define val second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define eps 1e-8
int n,dp[310][310][2],sum[310];
pii a[310];
int g[310][310];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int gcd(int x,int y)
{
if (!y) return x;
else return gcd(y,x%y);
}
signed main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();
rep(i,1,n)
{
a[i].ky=read();a[i].val=read();
}
sort(a+1,a+1+n);
rep(i,1,n) rep(j,1,n) g[i][j]=gcd(a[i].ky,a[j].ky);
rep(i,1,n) sum[i]=sum[i-1]+a[i].val;
memset(dp,-0x3f,sizeof(dp));
rep(i,1,n)
{
if ((i>1) && (g[i][i-1]!=1)) dp[i][i][0]=a[i].val;
if ((i<n) && (g[i][i+1]!=1)) dp[i][i][1]=a[i].val;
}
ll ans=dp[0][0][0];
rep(len,2,n)
{
for (int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
rep(k,i,j)
{
ll tmp;
if (k==i) tmp=dp[i+1][j][0]+sum[j]-sum[i-1];
else if (k==j) tmp=dp[i][j-1][1]+sum[j]-sum[i-1];
else tmp=dp[i][k-1][1]+dp[k+1][j][0]+sum[j]-sum[i-1];
if ((i>1) && (g[k][i-1]!=1)) dp[i][j][0]=max(tmp,dp[i][j][0]);
if ((j<n) && (g[k][j+1]!=1)) dp[i][j][1]=max(tmp,dp[i][j][1]);
if (len==n) ans=max(ans,tmp);
}
}
}
if (ans<0) puts("-1");
else printf("%lld",ans);
return 0;
}
T3
30pts应该是暴力枚举区间同时暴力(check),(O(n^3))
60pts就是枚举进行旋转区间的最中间的元素(即旋转中心),这样的话可以将一部分(check)放到一起,(O(n^2))
满分的话首先考虑到这样一件事情,合法的区间可以被优化到(O(n))级别,因为假设当前有一个旋转区间([l,r]),若(a_l eq r)且(a_r eq l)的话,我们交换(l,r)就是无意义的,于是整个区间都可以往里面缩,最后我们枚举的区间肯定是形如([i,a_i])或([a_i,i])的
接下来就是优化(check),借鉴(60pts)的思想,我们把相同旋转中心的区间放在一起,按照长度排序,假设我们当前枚举的旋转区间是([l,r]),那么答案就是(sum(1,l-1)+nowsum(l,r)+sum(r+1,n)),(sum(i,j))表示([i,j])中的合法点数,注意到(nowsum)的值的增加只有可能是因为旋转,假设这是按照长度排序的第(k)个区间,那么(nowsum(i,j)=k)(即使当前(l,r)交换对答案有2的贡献,我们也会在枚举下一个区间的时候考虑到)
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef long double db;
const int N=10000;
const db pi=acos(-1.0);
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define fir first
#define sec second
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)
#define maxd 998244353
#define eps 1e-8
int n,a[100100],pos[100100],sum[100100];
vector<int> rotlen[200200];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void solve1()
{
int sum=0;
rep(i,1,n)
{
a[i]=read();sum+=(a[i]==i);
}
int ans=sum;
rep(i,1,n)
{
int l=i-1,r=i+1,tmp=sum;
while ((l>=1) && (r<=n))
{
if (a[l]==r) tmp++;
if (a[r]==l) tmp++;
if (a[l]==l) tmp--;
if (a[r]==r) tmp--;
ans=max(tmp,ans);
l--;r++;
}
}
rep(i,1,n)
{
int l=i-1,r=i,tmp=sum;
while ((l>=1) && (r<=n))
{
if (a[l]==r) tmp++;
if (a[r]==l) tmp++;
if (a[l]==l) tmp--;
if (a[r]==r) tmp--;
ans=max(tmp,ans);
l--;r++;
}
}
printf("%d",ans);
}
void solve2()
{
rep(i,1,n)
{
a[i]=read();sum[i]=sum[i-1];
if (a[i]==i) sum[i]++;
}
rep(i,1,n)
rotlen[i+a[i]].pb(abs(i-a[i])+1);
int ans=sum[n];
rep(i,1,n*2)
{
if (!rotlen[i].size()) continue;
sort(rotlen[i].begin(),rotlen[i].end());
int len=rotlen[i].size();
rep(j,0,len-1)
{
int now=rotlen[i][j],l,r;
if (i%2==0)
{
l=i/2-now/2;r=i/2+now/2;
}
else
{
l=i/2-now/2+1;
r=i/2+now/2;
}
ans=max(ans,j+1+sum[l-1]+sum[n]-sum[r]);
}
}
printf("%d",ans);
}
int main()
{
//freopen("rotate.in","r",stdin);
//freopen("rotate.out","w",stdout);
n=read();
solve2();
return 0;
}