Public NOIP Round #3 题解

PJudge#3 题解

A 移除石子

题意

     n 个石子摆在二维平面上,求构造方案,使得每次框定一个边与坐标轴平行正方形(允许非整点顶点)并拿走其中(包括边上)的石子的数量总为 2.或说明无解
     1n3000.

题解

     下发SPJ:

string can=ouf.readToken();
if (lowerCase(can) != "yes"){quitf(_wa, "The participant does not have a solution");}

     不会是真的吧。

     SPJ已经告诉我们所有情况都是有解的了,所以我们只需要专心想怎么构造了。

     如果我们一开始就往中间取拿必然会束手束脚的,所以我们不妨把石子从左往右,从上到下依次考虑:

     如果一个石子下面有石子:把这两个石子一并取走,往左上方套一个很大的正方形即可。

     如果一个石子右边有石子:

  • 如果紧接着的石子在右下方或正右方:那这两个一并取走。

  • 如果紧接着的石子在右上方:考虑是否有石子在这两个石子之间,如果有那就把右边的两个石子取走,否则就直接取走。

     其中取右边石子的时候要注意为了防止右边的第二个石子跟当前石子在同一个 y ,应该把正方形向右平移 0.1

     corner 很多,但是随便写写甚至错解都能过 90.

     具体实现见代码:

代码

查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
template<typename T>void print(T x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
struct node{
	int x,y;
	node(){}
	node(int X,int Y){x=X,y=Y;}
}P[3005];
bool vis[3005];
bool cmp(node a,node b){return a.x!=b.x?a.x<b.x:a.y>b.y;}
int main() {
    #ifndef ONLINE_JUDGE
    freopen("input","r",stdin);
    freopen("output","w",stdout);
    #endif
	int T,n,Case=1; read(T);
	while(T--)
	{
		puts("Yes");
		read(n);
		for(int i=1;i<=n;i++) read(P[i].x),read(P[i].y),vis[i]=false;
		sort(P+1,P+1+n,cmp);
		// for(int i=1;i<=n;i++) cerr<<P[i].x<<" "<<P[i].y<<endl;
		// if(Case==23)
		// {
		// 	cerr<<n<<endl;
		// 	for(int i=1;i<=n;i++) cerr<<P[i].x<<" "<<P[i].y<<endl;
		// }
		for(int i=1;i<=n;i++)
		{
			if(vis[i]) continue;
			int X1=P[i].x,Y1=P[i].y;
			int X2=114,Y2=514,pos2=n+1;
			for(int j=i+1;j<=n;j++) if(!vis[j]) {pos2=j; X2=P[pos2].x; Y2=P[pos2].y;break;}
			if(X1==X2) printf("%d %d %lld %lld\n",X1,Y2,1ll*X1-2000000000ll,1ll*Y2+2000000000ll),vis[i]=vis[pos2]=true;
			else
			{
				int X3=P[i+2].x,Y3=P[i+2].y,pos3=n+1;
				for(int j=pos2+1;j<=n;j++) if(!vis[j]) {pos3=j; X3=P[pos3].x; Y3=P[pos3].y;break;}
				while(Y3>=Y1&&pos3<=n&&X2==X3)
				{
					// if(i==93)
					// {
					// 	cerr<<i<<" "<<pos2<<" "<<pos3<<endl;
					// }
					if(Y3!=Y1||X3-(Y2-Y3)>=X1)
					{
						printf("%.1f %d %.1f %d\n",X3-(Y2-Y3)+0.1,Y2,X3+0.1,Y3);
						vis[pos2]=vis[pos3]=true;int Tmpp=pos3;pos2=pos3=n+1;
						for(int j=Tmpp+1;j<=n;j++) if(!vis[j]) {pos2=j; X2=P[pos2].x; Y2=P[pos2].y;break;}
						for(int j=pos2+1;j<=n;j++) if(!vis[j]) {pos3=j; X3=P[pos3].x; Y3=P[pos3].y;break;}
					}
					else break;
				}
				if(Y1==Y3&&X2==X3)
				{
					printf("%d %d %d %d\n",X1,Y1,X3,Y1+(X3-X1));
					vis[i]=vis[pos3]=true;
				}
				else if(pos3>n||Y3<Y1||X2!=X3)
				{
					printf("%lld %lld %lld %lld\n",1ll*max(X1,X2),1ll*min(Y1,Y2),1ll*max(X1,X2)-2000000000ll,1ll*min(Y1,Y2)+2000000000ll);
					vis[i]=vis[pos2]=true;
				}
			}
		}
		Case++;
	}
	return 0;
}

B 抓内鬼

题意

     无向图,点有点权。求将图上 k 个点染成 Unk 个点染成 P 使得 1n 的最短路上总有一条边两个端点同色的方案或说明无解。
     1n105

题解

     发现如果 1,n 之间直接有连边那就把 1,n 染成同色,其余随意即可。注意到这只在 n=2,k=1 的时候会失效,事实上这也是唯一无解的情况。

     否则我们把靠近 n 附近的所有最短路经过的点染成与 n 同色。其余点染成异色(保证 1 为异色,其余随意)即可。

     在这种情况下,如果染 n 附近的点不够,那剩下的 nk 就必定会把 1 附近染满。

     很是诈骗。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
template<typename T>void print(T x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int n,m,k;
bool Vis[100005];
int Val[100005],Dis[100005];
vector <int> G[100005];
struct cmp{
	bool operator()(const PII x,const PII y){return x.second>y.second;}
};
priority_queue<PII,vector<PII>,cmp>Q;
void Dij()
{
	for(int i=1;i<=n;i++) Dis[i]=2e9;
	Q.push(mp(1,Dis[1]=0));
	while(!Q.empty())
	{
		int u=Q.top().first;Q.pop();
		if(Vis[u]) continue; Vis[u]=true;
		for(int i=0;i<G[u].size();i++)
		{
			int v=G[u][i];
			if(Dis[v]>Dis[u]+Val[v])
			{
				Dis[v]=Dis[u]+Val[v];
				Q.push(mp(v,Dis[v]));
			}
		}
	}
}
char Ans[100005];
int main() {
	// freopen("catch.in","r",stdin);
	// freopen("catch.out","w",stdout);
	bool Con=false;
	read(n);read(m);read(k);
	int P=n-k,U=k;
	for(int i=1;i<=n;i++) read(Val[i]);
	for(int i=1,u,v;i<=m;i++)
	{
		read(u);read(v);
		G[u].push_back(v);
		G[v].push_back(u);
		if((u==1&&v==n)||(u==n&&v==1)) Con=true;
	}
	if(n==2&&k==1) return puts("impossible")&0;
	if(P==n||U==n)
	{
		for(int i=1;i<=n;i++)
		{
			if(P) Ans[i]='P';
			else Ans[i]='U';
		}
		return puts(Ans+1)&0;
	}
	if(Con)
	{
		if(P>=2) Ans[1]=Ans[n]='P',P-=2;
		else if(U>=2) Ans[1]=Ans[n]='U',U-=2;
		else return puts("impossible")&0;
		for(int i=2;i<n;i++)
		{
			if(P) Ans[i]='P',P--;
			else Ans[i]='U';
		}
		return puts(Ans+1)&0;
	}
	Dij();
	vector <int> V;
	for(int i=1;i<=n;i++) Vis[i]=false;
	for(int j=0;j<G[n].size();j++)
	{
		int v=G[n][j];
		if(Dis[v]+Val[n]==Dis[n]) V.push_back(v),Vis[v]=true;
	}
	if(U<P) Ans[1]='U',U--,Ans[n]='P',P--;	
	else Ans[1]='P',P--,Ans[n]='U',U--;
	for(int i=0;i<V.size();i++)
	{
		if(Ans[n]=='U')
		{
			if(U) Ans[V[i]]='U',U--;	
			else Ans[V[i]]='P',P--;
		}
		else
		{
			if(P) Ans[V[i]]='P',P--;
			else Ans[V[i]]='U',U--;
		}
	}
	for(int i=2;i<n;i++)
	{
		if(!Vis[i])
		{
			if(P) Ans[i]='P',P--;
			else Ans[i]='U',U--;
		}
	}
	puts(Ans+1);
	return 0;
}

C 异或序列

题意

     求长度在 [1,n] ,元素大小在 [1,n] 且任意三个相邻的元素异或和不为 0 的递增序列数量。
1n106.

题解

     朴素dp:记 fi 表示以 i 结尾的序列的方案。

     那么 fi=1+j<ifjg(i,j) ,其中 g(i,j) 表示在 i 处第一次遇见不合法的异或值的方案。

     很显然每次可以 n2 计算(枚举 j ,然后 ij <此处应保证 ij<i >处转移过来)。但是我们注意到如果 j 的二进制最高位与 i 相等那必然有 ij<i。那我们可以枚举二进制最高位在哪里和 i 不同,然后这样的 j 剩下部分随便填,也就是一个分为 logn 段的前缀和。而 f 也显然可以前缀和。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
int High[1000005],MOD;
ll f[1000005],g[1000005],Sum[1000005];
inline int Add(int x,int y){return (x+y)%MOD;}
inline int Dec(int x,int y){return (x-y+MOD)%MOD;}
inline int Mul(int x,int y){return 1ll*x*y%MOD;}
int qpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1) ret=Mul(ret,a);
        b>>=1;a=Mul(a,a);
    }
    return ret;
}
int main() {
    #ifndef ONLINE_JUDGE
    freopen("input","r",stdin);
    freopen("output","w",stdout);
    #endif
    int n;read(n);read(MOD);
    for(int i=1;i<=n;i++)
    {
        if(i&(i-1)) High[i]=High[i-1];
        else High[i]=i;
    }
    ll Tmp=0;
    for(int i=1;i<=n;i++)
    {
        if(!(i&(i-1))) Tmp=0;
        f[i]=Mul(Add(Sum[High[i]-1],1),Dec(i,High[i]));
        for(int j=i^High[i];j;j^=High[j])
            f[i]=Dec(f[i],Dec(Sum[(High[j]<<1)-1],Sum[High[j]-1]));
        g[i]=Add(f[i]+1,Add(Sum[High[i]-1],Tmp));
        Tmp=Add(Mul(Tmp,2),f[i]);
        Sum[i]=Add(Sum[i-1],g[i]);
    }
    printf("%lld",Sum[n]);
    return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
*/

D 数圈圈

题意

    n×m 的字符矩阵有多少圈。圈的定义为矩形(长、宽>2)的四条边均为同一字符。
     1n,m2000.

题解

     没有心思写了,暂时缓缓吧。

posted @   Azazеl  阅读(158)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示