2023.11.8测试

NOIP模拟赛-2023.11.8

T1 日本题一

n×m 的网格,从 (1,1) 移到 (n,m)

网格间有两种连边(无向边):一类边 (i,j)(i+1,j);二类边 (i,j)(i,j+1)。所有边的代价都是 1,但初始时二类边均不能通过

网络中有 k 个节点可以变换边的使用权限,将通过的变成不通过,不通过的变成通过,变换一次代价 1。求最小代价,不能到则输出 1

2n,m1051k2×105

由于一开始题面有问题所以没写,后来看错了写了递推,最后半小时才开始写

其实就是分层图最短路

code
#include<bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
using namespace std;

const int N=1e5+10,M=5e5+10;

int n,m,k,tor[M],toc[M];
LL ans,f[M<<1];
struct node{int x,y,id,pd;}a[M];
vector <node> r[N],c[N];
vector <pii> g[M];
bool flag1,flag2,vis[M];

bool cmp2(const node &A,const node &B)
{
	if(A.y==B.y)
		return A.x<B.x;
	return A. y<B.y;
}

bool cmp1(const node &A,const node &B)
{
	if(A.x==B.x)
		return A.y<B.y;
	return A.x<B.x;
}

void add1(int x,int y,int z,int pd1,int pd2)
{

	g[x].push_back({y,z});
	g[y].push_back({x,z});
	if(pd1)
		g[x+k].push_back({y,z+1});
	if(pd2)
		g[y+k].push_back({x,z+1});
}

void add2(int x,int y,int z,int pd1,int pd2)
{
	// cout<<x<<" "<<y<<" "<<z<<endl;
	g[x+k].push_back({y+k,z});
	g[y+k].push_back({x+k,z});
	if(pd1)
		g[x].push_back({y+k,z+1});
	if(pd2)
		g[y].push_back({x+k,z+1});
}

void dij()
{
	priority_queue < pair<LL,int> > q;
	q.push({0,a[1].id});  f[a[1].id]=0;
	if(a[1].pd)
		q.push({1,a[1].id+k}),f[a[1].id+k]=1;
	while(q.size())
	{
		int x=q.top().second;  q.pop();
		if(vis[x])
			continue;
		vis[x]=1;
		for(auto v:g[x])
		{
			if(f[v.first]>f[x]+(LL)v.second)
				f[v.first]=f[x]+(LL)v.second,q.push({-f[v.first],v.first});
		}
	}
}

int main()
{
	freopen("jpa.in","r",stdin);
	freopen("jpa.out","w",stdout);

	memset(f,0x3f,sizeof(f));

	scanf("%d%d%d",&n,&m,&k);
	swap(n,m);
	for(int i=1; i<=k; i++)
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		swap(a[i].x,a[i].y);
		flag1|=(a[i].x==1&&a[i].y==1);
		flag2|=(a[i].x==n&&a[i].y==m);
		a[i].pd=1;  a[i].id=i;
	}

	if(!flag1)
		a[++k]={1,1,k,0};
	if(!flag2)
		a[++k]={n,m,k,0};
	// for(int i=1; i<=n; i++)
	// 	a[++k]={i,m,k,0};
	// for(int i=1; i<=m; i++)
	// 	a[++k]={n,i,k,0};

	sort(a+1,a+1+k,cmp2);
	for(int i=1; i<=k; i++)
	{
		if(a[i+1].y==a[i].y)
			add1(a[i].id,a[i+1].id,a[i+1].x-a[i].x,(a[i].pd==1||i==1),a[i+1].pd);
	}

	sort(a+1,a+1+k,cmp1);
	for(int i=1; i<=k; i++)
	{
		if(a[i+1].x==a[i].x)
			add2(a[i].id,a[i+1].id,a[i+1].y-a[i].y,a[i].pd,a[i+1].pd);
	}

	dij();

	ans=min(f[a[k].id],f[a[k].id+k]);
	if(ans>1e15)
		puts("-1");
	else
		printf("%lld\n",ans);
	
	return 0;
}

T2 毛子题

CF1582F2 Korney Korneevich and XOR (hard version)

easy version 还是比较好搞的,设 fi,j 表示前 i 位是否存在异或和为 j 的上升子序列,枚举 j,转移即找到一个 fk,jai=1kak<ai,那我们可以记录 tv 表示最小的产生异或和为 v 的上升子序列的结尾元素,枚举时只需判断 tjai 是否小于 ai 即可,并用 ai 去更新 tj

对于 hard version 来说,原先的 dp 状态转移已经很难再优化,考虑换一个状态。因为其实我们从前往后枚举时并不关心当前的位置,只关心产生异或和的那个上升子序列的结尾元素,所以设 fj,k 表示是否存在以 <j 的元素结尾的上升子序列异或和为 k。对于当前的数 ai,枚举 k,若 fai,k=1,则更新 fj,kai,其中 ai<jV

实际上,我们并没有必要去更新所有的 j(ai,V],因为若我们知道了 fj,k=1 则所有 >jj 均满足 fj,k=1。所以记 mxv 表示对于 j(mxv,V] 来说,均有 fj,v=1。所以下次遇到 aik=v 时,只需从 ai+1 枚举到 mxv,然后用 ai 更新 mxv 即可

我们通过上述操作,优化了更新的复杂度。考虑优化枚举的复杂度。对于同一个 aifai,kk 枚举过了就没有必要再枚举,fai,k=0 的也没必要枚举。故开一个桶 bucai 表示 ai 还没有枚举过且 fai,k=1k,每次遇到 ai 就枚举 bucai 里的数然后更新 kai,最后把 bucai 清空

时间复杂度 O(n+V2)

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10,V=8192;

int n,a[N];
int mx[V],vis[V],cnt;
vector <int> buc[V];

int main()
{
	freopen("ru.in","r",stdin);
	freopen("ru.out","w",stdout);
	
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);

	vis[0]=1;
	for(int i=0; i<V; i++)
		mx[i]=V-1,buc[i].push_back(0);

	for(int i=1; i<=n; i++)
	{
		for(int k:buc[a[i]])
		{
			int cur=k^a[i];
			vis[cur]=1;
			while(mx[cur]>a[i])
				buc[mx[cur]--].push_back(cur);
		}
		buc[a[i]].clear();
	}

	for(int i=0; i<V; i++)
		if(vis[i])
			cnt++;
	printf("%d\n",cnt);
	for(int i=0; i<V; i++)
		if(vis[i])
			printf("%d ",i);
	
	return 0;
}

T3 欧洲小国题

P8313 [COCI2021-2022#4] Izbori

一眼原题,但是树状数组写了调了 2.5h 无果

注意维护三阶前缀和的写法

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10,M=4e5+10;

int n,m,a[N],b[N],siz;
LL ans;
vector <int> pos[N];

struct BIT
{
	LL c1[M],c2[M],c3[M];

	void add(int x,int y)
	{
		x+=n+3;
		for(int i=x; i<=siz; i+=(i&-i))
		{
			c1[i]+=1LL*y;
			c2[i]+=1LL*y*x;
			c3[i]+=1LL*y*x*x;;
		}
	}

	LL query(int x)
	{
		x+=n+3;  LL res=0;
		for(int i=x; i; i-=(i&-i))
			res+=1LL*(x+2)*(x+1)*c1[i]-1LL*(2*x+3)*c2[i]+c3[i];
		return res/2;
	}
}t;

int main()
{
	freopen("eu.in","r",stdin);
	freopen("eu.out","w",stdout);

	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]),b[i]=a[i];

	sort(b+1,b+1+n);
	m=unique(b+1,b+1+n)-(b+1);

	for(int i=1; i<=m; i++)
		pos[i].push_back(0);
	for(int i=1; i<=n; i++)
		a[i]=lower_bound(b+1,b+1+m,a[i])-b,pos[a[i]].push_back(i);
	for(int i=1; i<=m; i++)
		pos[i].push_back(n+1);

	siz=2*n+5;
	for(int i=1; i<=m; i++)
	{
		int len=pos[i].size();
		for(int j=0; j<len-1; j++)
		{
			int L=pos[i][j],R=pos[i][j+1]-1;
			int x=2*j-L,y=x-(R-L+1)+1;  //y<x
			if(L>R)
				continue;
			ans+=t.query(x-1)-t.query(y-2);
			t.add(y,1);  t.add(x+1,-1);
		}
		for(int j=0; j<len-1; j++)
		{
			int L=pos[i][j],R=pos[i][j+1]-1;
			int x=2*j-L,y=x-(R-L+1)+1;  //y<x
			if(L>R)
				continue;
			t.add(y,-1);  t.add(x+1,1);
		}
	}

	printf("%lld\n",ans);
	
	return 0;
}

T4 日本题二

原:https://atcoder.jp/contests/cf16-exhibition/tasks/codefestival_2016_ex_b

其实就是求所有可能的和在十进制下第 i 的最大值

枚举 i,由数据范围的限制最多只用枚举到 17

不难想到背包,在 ai 比较小的时候可以直接先做背包再枚举 i,应该可以获得 4050

但现在 ai 达到了 1017 级别,所以考虑在模 10i 的意义下做背包

但是直接做我们仍然会产生非常多的状态,但其实很多状态是无用的,因为我们只关心第 i 位的情况,并不关心其它位置。设有三个合法状态 x<y<zzx10i1z,x 的第 i 位的差 1,这时我们就可以说 y 是无用的。为什么?因为若后面它们同时加上 v,则 x+v,y+v,z+v 这三个新状态仍然满足 (z+v)(x+v)10i1,所以 y 这个状态的第 i 位肯定和 x,z 中的一个相同,我们就可以舍弃这个状态。因此每个时刻合法状态的数量都不超过 10

时间复杂度 O(17×n×10log10)

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e4+10;

int n,m,tot;
LL a[N],S[N],ans;

int main()
{
	freopen("jpb.in","r",stdin);
	freopen("jpb.out","w",stdout);

	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%lld",&a[i]);

	LL pw=1;
	for(int i=1; i<=17; i++)
	{
		LL p=pw*10;  S[m=1]=0;
		for(int j=1; j<=n; j++)
		{
			for(int k=1; k<=m; k++)
				S[k+m]=(S[k]+a[j])%p;//,cout<<S[k+m]<<" ";
			sort(S+1,S+1+2*m);
			tot=2;
			for(int k=3; k<=2*m; k++)
			{
				// cout<<S[k]<<" ";
				if(S[k]-S[tot-1]<=pw)
					S[tot]=S[k];
				else
					S[++tot]=S[k];
			}
			// cout<<endl;
			m=tot;
		}
		ans+=S[m]/pw;
		// cout<<S[m]<<endl;
		pw*=10;
	}

	printf("%lld\n",ans);

	return 0;
}
posted @   xishanmeigao  阅读(25)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示