合集:NJPC2017

太长了不放缺省源了,代码都只有主程序部分,不知道这个风格怎么样。

个人认为难度顺序:A < B < C < E < F < H < D < G。

A 入力フォーム/洛谷/AT

\(L\)\(|S|\) 取较小值,输出前这些位即可,复杂度 \(\mathcal O(\min(L,|S|))\)

namespace LgxTpre
{
    static const int MAX=500010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
	int n,m;
	char s[MAX];
	
    inline void lmy_forever()
    {
    	scanf("%lld%s",&n,s+1),m=strlen(s+1);
    	for(int i=1;i<=min(n,m);++i) putchar(s[i]);
    	return puts(""),void();
	}
}

B 格子グラフ/洛谷/AT

如果一个 x 都没有,那么答案为 \(H \times (W - 1) + W \times (H - 1)\)。当加入一个 x 时,它会使四周合法的边被标记,合法指的是指向的点存在且未被标记过。可以通过 map 来存储坐标查询点之前是否被标记过,复杂度 \(\mathcal O(n \log n)\)

namespace LgxTpre
{
    static const int MAX=0010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
	int n,m,k,x,y,ans;
	map<pii,bool> v;
	constexpr int dx[]={0,0,1,-1};
	constexpr int dy[]={1,-1,0,0};
	
    inline void lmy_forever()
    {
    	read(n,m,k),ans=n*(m-1)+m*(n-1);
    	auto ok=[&](int x,int y)->bool{return x>=1&&x<=n&&y>=1&&y<=m&&v.find(mp(x,y))==v.end();};
    	for(int i=1;i<=k;++i) {read(x,y),v[mp(x,y)]=1; for(int j=0;j<4;++j) ans-=ok(x+dx[j],y+dy[j]);}
		write(ans,'\n');
	}
}

C ハードル走/洛谷/AT

类似于 CF1873D,当遇到一个跨栏时是必须要跳的,维护当前所在的位置,然后再向后跳 \(L\) 的距离看会不会到下一个跨栏,贪心加单指针暴力后跳即可。复杂度 \(\mathcal O(n)\)

namespace LgxTpre
{
    static const int MAX=100010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    int n,L,a[MAX],now;
	
    inline void lmy_forever()
    {
    	read(n,L);
    	for(int i=1;i<=n;++i) read(a[i]);
    	int i=1;
    	while(i<=n)
    	{
    		int j=i;
    		while(j<n&&a[j+1]<L+a[i]) ++j;
    		cmax(now,now+L,a[j]),now+=L,i=j+1;
    		if(i<=n&&a[i]<=now) return puts("NO"),void();
		}
		puts("YES");
	}
}

D NMパズル/洛谷/AT

根本想不到啊,感觉挺神秘的。首先我们让行上能够拥有的逆序对数尽可能多,即如果 \(n > m\),就交换 \(n,m\),最后输出再换过来就好了。一个矩阵最多能拥有的逆序对数为 \(A = \dfrac{n \times m \times (m - 1)}{2} + \dfrac{m \times n \times (n - 1)}{2}\),注意到此时行上贡献的逆序对数一定是大于等于 \(\dfrac{A}{2}\) 的。如果 \(k \leq \dfrac{A}{2}\),那么初始矩阵从左到右从上到下从小到大的填数,此时逆序对数为 \(0\);否则从大到小的填数,此时逆序对数为 \(A\)。此时就不需要考虑列上的贡献,只需要对每一行分别操作。按照冒泡排序的操作顺序,前者每次可以增加一个逆序对,后者则是每次减少一个。这一部分暴力构造即可,视 \(n,m\) 同阶,复杂度是 \(\mathcal O(n^3)\) 的。

namespace LgxTpre
{
    static const int MAX=110;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    int n,m,K,fl,all;
    int a[MAX][MAX];
	
    inline void lmy_forever()
    {
    	read(n,m,K),all=n*m*(m-1)/2+m*n*(n-1)/2;
    	if(n>m) Swp(n,m),fl=1;
    	if(K<=all/2) for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=(i-1)*m+j;
		if(K>all/2) {for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=n*m-((i-1)*m+j)+1; K=all-K;}
		for(int i=1;K&&i<=n;++i) for(int j=m-1;K&&j;--j) for(int k=1;K&&k<=j;++k) --K,Swp(a[i][k],a[i][k+1]);
		if(!fl) for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) write(a[i][j],j==m?'\n':' ');
		if(fl)  for(int i=1;i<=m;++i) for(int j=1;j<=n;++j) write(a[j][i],j==n?'\n':' ');
	}
}

E 限界集落/洛谷/AT

树形 DP 杂糅题。根据直径定理,一棵无根树以任意节点为根,最长根链一定取自任意一条直径的两个端点之一。可以直接两遍 dfs 求出一条直径的两个端点,然后倍增暴力求出以每个节点为根的最长根链长度。剩下的部分是 CF219D,先一次 dfs 求出以 \(1\) 为根有哪些边需要翻转;再一遍 dfs 根据子树大小计算以其它节点为根的答案。最后对所有合法情况取最小值即可。复杂度 \(\mathcal O(n \log n)\),瓶颈在于树上倍增求两点间距离,不过换根 DP 也能求出以每个节点为根的最长根链,这样就是 \(\mathcal O(n)\) 的了。

namespace LgxTpre
{
    static const int MAX=100010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    int n,d,ans,A,B,x,y,z;
    int f[MAX],fa[MAX][20],tag[MAX];
    int dep[MAX],dis[MAX],dps;
    vector<pair<pii,int>> G[MAX];
    #define to it.fi
    #define val it.se
	
    inline void lmy_forever()
    {
    	read(n,d),ans=INF;
    	for(int i=1;i<n;++i) read(x,y,z),G[x].eb(mp(mp(y,z),0)),G[y].eb(mp(mp(x,z),1));
    	
    	auto GetDiameter=[&](auto GetDiameter,int now,int father)->void
    	{
    		if(dep[now]>dep[dps]) dps=now;
    		for(auto [it,tow]:G[now]) if(to!=father) dep[to]=dep[now]+val,GetDiameter(GetDiameter,to,now);
		};
		GetDiameter(GetDiameter,1,0),A=dps,GetDiameter(GetDiameter,dps,0),B=dps;
		
		auto dfs1=[&](auto dfs1,int now,int father)->void
		{
			fa[now][0]=father,dep[now]=dep[father]+1;
			for(int i=1;i<=__lg(dep[now]);++i) fa[now][i]=fa[fa[now][i-1]][i-1];
			for(auto [it,tow]:G[now]) if(to!=father) dis[to]=dis[now]+val,dfs1(dfs1,to,now),tag[to]=!tow;
		};
		dfs1(dfs1,1,0); for(int i=1;i<=n;++i) f[1]+=tag[i];
		
		auto dfs2=[&](auto dfs2,int now,int father)->void
		{
			for(auto [it,tow]:G[now]) if(to!=father) f[to]=f[now]+(tow?1:-1),dfs2(dfs2,to,now);
		};
		dfs2(dfs2,1,0);
		
		auto LongestChain=[&](int p)->int
		{
			auto LCA=[&](int x,int y)->int
			{
				if(dep[x]<dep[y]) Swp(x,y);
        		while(dep[x]>dep[y]) x=fa[x][__lg(dep[x]-dep[y])];
        		if(x==y) return x;
        		for(int i=__lg(dep[x]);~i;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        		return fa[x][0];
			};
			
			int lcaA=LCA(A,p),lcaB=LCA(B,p);
			return max(dis[A]+dis[p]-dis[lcaA]*2,dis[B]+dis[p]-dis[lcaB]*2);
		};
		for(int i=1;i<=n;++i) if(LongestChain(i)<=d) cmin(ans,f[i]);
		
		write(ans==INF?-1:ans,'\n');
	}
}

F ダブルス/洛谷/AT

求极值,先二分答案转成判定性问题。一个非常经典的思路是如果能够完成 \(X_{i + 1}\) 上的操作,那么必然有一个点在 \(X_{i}\) 上,于是只需要考虑如何刻画另一个人所在的位置。不妨记完成 \(X_{i + 1}\) 的人为 \(P\),另一个人为 \(Q\)。因为每个时刻 \(Q\) 可以往两个方向走,所以容易发现 \(Q\) 可以存在的位置是一个区间,那么起初是 \(L = R = 0\)。记当前二分出的速度为 \(V\),从当前球过来的时刻到下一个球过来的时刻为 \(t = T_{i + 1} - T_{i}\),那么这段时间可以走 \(S = Vt\) 的路程,于是 \(P\) 可以到达 \([X_i - S,X_i + S]\)\(Q\) 可以到达 \([L - S,R + S]\)。如果这两个区间都不能包含 \(X_{i + 1}\),那么说明到不了了,返回无解。如果 \(P\) 能到达,\(Q\) 不能到达,那么 \(Q\) 就可以接着行走,下一个时刻 \(Q\) 可以出现的区间扩展为 \(L = L - S,R = R + S\)。如果 \(Q\) 能到达,\(P\) 不能到达,那么两个人的身份就要互换了,新的 \(Q\) 的区间变为了 \(L = X_i - S,R = X_i + S\)。如果两者都能到达,那么 \(L\)\(R\) 的取值对上面两种情况分别贪心的取最值即可。要做实数域上的二分,check 是 \(\mathcal O(n)\) 的,于是复杂度为 \(\mathcal O(n \log V)\)

namespace LgxTpre
{
    static const int MAX=100010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    int n,T[MAX],X[MAX];
    double l,r,mid;
    constexpr double eps=1e-9;
	
    inline void lmy_forever()
    {
    	read(n),l=0.0,r=1e7;
    	for(int i=1;i<=n;++i) read(T[i],X[i]);
    	auto check=[&](double V)->bool
    	{
    		double L=0,R=0;
    		for(int i=0;i<n;++i)
    		{
    			double s=V*((double)(T[i+1]-T[i]));
    			bool bet=(i&&fabs(X[i+1]-X[i])<=s+eps),nbet=(L-s<=X[i+1]+eps&&X[i+1]-eps<=R+s);
    			if(!bet&&!nbet) return 0;
    			if(bet&&!nbet) L-=s,R+=s;
    			if(!bet&&nbet) L=X[i]-s,R=X[i]+s;
    			if(bet&&nbet) L=min(L-s,X[i]-s),R=max(R+s,X[i]+s);
			}
			return 1;
		};
    	while(l+eps<r) {mid=(l+r)/2.0; if(check(mid)) r=mid; else l=mid;}
    	printf("%.9lf\n",r);
	}
}

G 交換法則/洛谷/AT

考虑什么样的串能够进行重构得到。假设说我们的串 \(S = a \cdots\cdots\),那么操作 \(a @ S_2 @ S_3 @ \cdots @ S_n\)有可能重构出 \(S\) 串。如果我们的串 \(S = \cdots a \cdots\),那么有两种操作:对以 \(a\) 开头的字符串和由 \(a\) 以外的字符串进行 \(@\) 操作,得到一个以 \(a\) 开头的字符串;对 \(a\) 和由 \(a\) 以外的字符串进行 \(@\) 操作,得到的还是一个以 \(a\) 开头的字符串。由此可知,上面那种情形无论如何都不能由重构得到。由此可以推广到,如果 \(c\) 是序列中最小的字符,\(S\) 是任意字符串,那么我们无法构造出 \(Sc\),证明是因为 \(c\) 是除了空序列之外字典序最小的串。将给定的串翻转,维护一个栈的结构,倒序考虑每一个字符。如果当前考虑到的字符字典序要小于栈顶那么就将其和栈顶合并。这样反复操作,如果最后栈中剩余多于一个字符串,那么无解,否则必定存在方法进行构造。

namespace LgxTpre
{
    static const int MAX=100010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    string s;
    vector<string> v;
	
    inline void lmy_forever()
    {
    	cin>>s,reverse(s.begin(),s.end());
    	for(auto c:s)
    	{
    		v.eb((string)""+c);
    		while(v.size()>1)
    		{
    			int n=v.size();
    			if(v[n-1]<=v[n-2]) v[n-2]=v[n-1]+v[n-2],v.pb(); else break;
			}
		}
		puts(v.size()>1?"No":"Yes");
	}
}

H 白黒ツリー/洛谷/AT

注意到子树颜色翻转是假的,因为边由两个端点的颜色异同性来决定,两个端点同时翻转并不会有影响,改变的只有翻转的那一个节点和它父亲间的边。有 LCT 惯用的套路,将边重编号建成点,衔接在原树上的两个端点。可以一遍 dfs 知道每条边的两个端点的颜色异同性,并完成对边重编号的工作。对于颜色相同的,即不合法的边,将其在 LCT 上的点权设为 \(1\)。于是翻转子树操作变成了 LCT 单点取反,注意要先将边在 LCT 上对应的点 makeroot 一下再修改。对于路径信息直接 split 出来查询路径上最大值是否为 \(1\) 即可。复杂度 \(\mathcal O(n \log n)\)

namespace LgxTpre
{
    static const int MAX=200010;
    static const int inf=2147483647;
    static const int INF=4557430888798830399;
    
    int n,q,N,op,u,v;
	int p[MAX],col[MAX],id[MAX];
    vector<int> G[MAX];
    
    namespace Link_Cat_Tree
    {
    	int fa[MAX],ch[MAX][2],rev[MAX],siz[MAX],val[MAX],mix[MAX];
    	inline bool get(int i)      {return ch[fa[i]][1]==i;}
    	inline bool noroot(int i)   {return ch[fa[i]][get(i)]==i;}
    	inline void pushup(int i)   {siz[i]=siz[ch[i][0]]+siz[ch[i][1]]+1,mix[i]=max({mix[ch[i][0]],mix[ch[i][1]],val[i]});}
    	inline void down(int i)     {if(!i) return; rev[i]^=1,Swp(ch[i][0],ch[i][1]);}
    	inline void pushdown(int i) {if(rev[i]) down(ch[i][0]),down(ch[i][1]),rev[i]=0;}
    	inline void pushall(int i)  {if(noroot(i)) pushall(fa[i]); pushdown(i);}
    	inline void rotate(int x)
    	{
    		int y=fa[x],z=fa[y]; bool k1=get(x),k2=get(y);
    		if(noroot(y)) ch[z][k2]=x;
			fa[x]=z;
    		ch[y][k1]=ch[x][!k1],fa[ch[x][!k1]]=y;
    		ch[x][!k1]=y,fa[y]=x;
    		pushup(y),pushup(x);
		}
		inline void splay(int x)
		{
			pushall(x);
			while(noroot(x))
			{
				int y=fa[x];
				if(noroot(y)) (get(x)^get(y))?rotate(x):rotate(y);
				rotate(x);
			}
		}
		inline void access(int x)      {for(int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,pushup(x);}
		inline void makeroot(int x)    {access(x),splay(x),down(x);}
		inline int  findroot(int x)    {access(x),splay(x); while(ch[x][0]) pushdown(x),x=ch[x][0]; return splay(x),x;}
		inline void split(int x,int y) {makeroot(x),access(y),splay(y);}
		inline void link(int x,int y)  {makeroot(x); if(findroot(y)!=x) fa[x]=y;}
		inline void cut (int x,int y)  {makeroot(x); if(findroot(y)==x&&fa[y]==x&&!ch[y][0]) fa[y]=ch[x][1]=0,pushup(x);}
	}
	using namespace Link_Cat_Tree;
	
    inline void lmy_forever()
    {
    	read(n),iota(siz+1,siz+2*n+1,1),N=n;
    	for(int i=2;i<=n;++i) read(p[i]),G[p[i]].eb(i);
    	for(int i=1;i<=n;++i) read(col[i]);
    	
    	auto dfs=[&](auto dfs,int now)->void
    	{
    		for(auto to:G[now]) id[to]=++N,link(now,N),link(N,to),val[N]=mix[N]=(col[now]==col[to]),dfs(dfs,to);
		};
		dfs(dfs,1);
		
		read(q);
		for(int i=1;i<=q;++i)
		{
			read(op);
			if(op==1) read(u),(u!=1?(makeroot(id[u]),val[id[u]]=1-val[id[u]],1):1);
			if(op==2) read(u,v),split(u,v),puts(mix[v]==1?"NO":"YES");
		}
	}
}
posted @ 2023-09-24 21:49  LgxTpre  阅读(61)  评论(0编辑  收藏  举报