11.26 NOIP2024模拟赛#28 div1

倒数力,大悲

T1

看样例+手推想到一个贪心,就是隔一个选一下

然鹅发现大小关系没有传递性

然后码的暴力

正解是尽量给短的较大的,如果两边长短不一样就把多的补给长的那一个

然后证明不知道(

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define db(x) cout<<"DEBUG "<<#x<<" = "<<x<<endl;
#define endl '\n'
using namespace std;

//#define SIZE (1<<20)
//char In[SIZE],Out[SIZE],*p1=In,*p2=In,*p3=Out;
//#define getchar() (p1==p2&&(p2=(p1=In)+fread(In,1,SIZE,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c-48);c=getchar();}
	return x*f;
}
template<typename _T>
inline void write(_T x)
{
	static _T sta[35];int top=0;
	if(x<0) putchar('-'),x=-x;
	do{sta[top++]=x%10,x/=10;}while(x);
	while(top) putchar(sta[--top]+'0');
}

template<int len>
struct __int
{
	int num[len*2+5];
	__int(){memset(num,0,sizeof(num));}
	inline void clear(){memset(num,0,sizeof(num));}
	inline __int operator+(__int y)
	{
		__int<len> res;
		fd(i,0,len)
		{
			res.num[i]+=num[i]+y.num[i];
			res.num[i+1]+=res.num[i]/10;
			res.num[i]%=10;
		}
		return res;
	}
	inline __int operator+(int y)
	{
		__int<len> res;
		res.num[0]=y;
		fd(i,0,len)
		{
			res.num[i]+=num[i];
			res.num[i+1]+=res.num[i]/10;
			res.num[i]%=10;
		}
		return res;
	}
	inline __int operator*(int y)
	{
		__int<len> res;
		fd(i,0,len)
		{
			res.num[i]+=y*num[i];
			res.num[i+1]+=res.num[i]/10;
			res.num[i]%=10;
		}
		return res;
	}
	inline __int operator*(__int y)
	{
		__int<len> res;
		fd(i,0,len)
		{
			fd(j,0,i) res.num[i]+=y.num[j]*num[i-j];
			res.num[i+1]+=res.num[i]/10;
			res.num[i]%=10;
		}
		return res;
	}
	inline __int operator/(int y)
	{
		__int<len>res=(*this);
		bd(i,len,0)
		{
			res.num[i-1]+=res.num[i]%y*10;
			res.num[i]/=y;
		}
		return res;
	}
	inline bool operator<(__int y)
	{
		bd(i,len,0) if(y.num[i]!=num[i]) return num[i]<y.num[i];
		return 0;
	}
	inline bool operator==(__int b)
	{
		return (!(b<(*this)))&&(!((*this)<b));
	}
	inline void debug()
	{
		int mxl=0;
		bd(i,len,0)	if(num[i]) {mxl=i;break;}
		bd(i,mxl,0) cerr<<num[i];
		cerr<<endl;
	}
	inline void print()
	{
		int mxl=0;
		bd(i,len,0)	if(num[i]) {mxl=i;break;}
		bd(i,mxl,0) putchar(num[i]+'0');
		putchar('\n');
	}
};

const int N=1e3+509,M=1e6+509,mod=998244353;

int a,b,c[N];
__int<N> aa,bb,ans;

inline void init()
{
	a=read(),b=read();
	if(a>b) swap(a,b);
	fd(i,1,9) c[i]=read();
}

void solve1()
{
	aa.clear(),bb.clear();
	int len1=a,len2=b,fl=0;
	for(int i=9;i;)
	{
		while(!c[i]&&i) --i;
		if(!i) break;
		--c[i];
		if(!len1||fl) bb.num[--len2]=i;
		else aa.num[--len1]=i;
		while(!c[i]&&i) --i;
		if(!i) break;
		--c[i];
		if(!len1||!fl) bb.num[--len2]=i;
		else aa.num[--len1]=i;
		if(aa.num[len1]>bb.num[len2]) fl=1;
//		aa.debug(),bb.debug();
	}
	ans.clear();
	ans=aa*bb;
    ans.print();
}

inline void Main()
{
	init();
	solve1();
}

signed main()
{
#define FJ
#ifdef FJ
	freopen("math.in","r",stdin);
	freopen("math.out","w",stdout);
#else
//	freopen("ex_math1.in","r",stdin);
//	freopen("math.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
	
	int T=read();
	while(T--) Main();
	
	return 0;
}

T2

很有思路,发现缩完点后一个联通块里可以互相推荐,然后 拓排+DP 即可

然后记 \(sum[i][j]\) 表示第 \(i\) 个联通块里选 \(j\) 个的最小值

\(h[i][j]\) 表示走到第 \(i\) 个块,已经选了 \(j\) 个的最小值

\(f[i][j]\) 表示在 \(i\) 块内出发并且在块内走了 \(j\) 个的最小值

转移的话就是:

\[h[x][i]=\min_{j\le i}(h[x][i],h[x][i-j]+f[x][j]); \]

然鹅我把

当你到达一个餐厅 \(i\),如果你持有这个餐厅老板推荐的店的用餐记录,你必须支付 \(x_i\) 块钱,否则需要支付 \(y_i\) 块钱。

理解成了:

当你到达一个餐厅 \(i\),如果你是被某个餐厅老板推荐来的,你必须支付 \(x_i\) 块钱,否则需要支付 \(y_i\) 块钱。

然后我还认为这个复杂度是 \(O(n^3)\) 的,所以码到一半就去码暴力了

正解其实就是这个,只不过是因为 \(O(n^3)\) 跑不满所以可以过

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define db(x) cout<<"DEBUG "<<#x<<" = "<<x<<endl;
#define endl '\n'
using namespace std;

//#define SIZE (1<<20)
//char In[SIZE],Out[SIZE],*p1=In,*p2=In,*p3=Out;
//#define getchar() (p1==p2&&(p2=(p1=In)+fread(In,1,SIZE,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c-48);c=getchar();}
	return x*f;
}

const int N=1e3+509,M=1e6+509,mod=998244353;

int n,m,k,ans[N];
int a[N],b[N];
int head[N],tot;
struct edge{int y,nxt;}e[M<<1];
inline void adde(int x,int y)
{
	e[++tot]={y,head[x]},head[x]=tot;
}

int cnt,dfn[N],low[N];
int st[N],top,ins[N];
vector<int> scc[N];
int sccsum,c[N],siz[N];
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	st[++top]=x,ins[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int y=e[i].y;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		++sccsum;
		scc[sccsum].push_back(0);
		int y;
		do
		{
			y=st[top--],ins[y]=0;
			c[y]=sccsum,
			scc[sccsum].push_back(y);
		}while(x!=y);
		siz[sccsum]=scc[sccsum].size()-1;
	}
}

vector<int> g[N];
int in[N];
bool fl[N];
void nw(int xx)
{
	fd(i,1,sccsum) fl[i]=0;
	fl[xx]=1;
	for(auto &x:scc[xx])
	{
		for(int i=head[x];i;i=e[i].nxt)
		{
			int y=c[e[i].y];
			if(!fl[y]) fl[y]=1,g[xx].push_back(y),++in[y];
		}
	}
}

int f[N][N],h[N][N],sum[N][N],p[M];
void pre()
{
	fd(i,1,sccsum)
	{
		sort(scc[i].begin(),scc[i].end(),[&](int x,int y){return a[x]<a[y];});
		fd(j,1,siz[i]) sum[i][j]=sum[i][j-1]+a[scc[i][j]],p[scc[i][j]]=j;
	}
}

void topu()
{
	queue<int> q;
	fd(i,1,sccsum)
	{
		fd(j,1,n) f[i][j]=h[i][j]=1e18;
		for(auto &x:scc[i])
		{
			if(x)
			{
				fd(j,0,siz[i]-1)
					f[i][j+1]=min(f[i][j+1],(p[x]>j?sum[i][j]:sum[i][j+1]-a[x])+b[x]);
			}
		}
		if(!in[i]) q.push(i);
	}
	while(!q.empty())
	{
		int x=q.front();q.pop();
		bd(i,n,0) fd(j,0,min(siz[x],i))
			h[x][i]=min(h[x][i],h[x][i-j]+f[x][j]);
		for(auto &y:g[x])
		{
			fd(k,0,n) h[y][k]=min(h[y][k],h[x][k]);
			if(!--in[y]) q.push(y);
		}
	}
}

inline void solve()
{
	fd(i,1,n) if(!dfn[i]) tarjan(i);
	pre();
	fd(i,1,sccsum) nw(i);
	topu();
	fd(i,1,n) ans[i]=1e18;
	fd(i,1,sccsum) fd(j,1,n) ans[j]=min(ans[j],h[i][j]);
	fd(i,1,n) if(ans[i]<1e18) printf("%lld\n",ans[i]);
}

signed main()
{
#define FJ
#ifdef FJ
	freopen("food.in","r",stdin);
	freopen("food.out","w",stdout);
#else
//	freopen("A.in","r",stdin);
//	freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
	
	n=read();
	fd(i,1,n)
	{
		a[i]=read(),b[i]=read();
		int d=read();
		fd(j,1,d)
		{
			int y=read();
			adde(i,y);
		}
	}
	
	solve();
	
	return 0;
}

T3

没看题(

正解是状压

先把初始状态和目标状态异或一下

然后注意到答案应该不会太大,而且是随机数据

定义 \(f_{i,s}\) 表示到第 \(i\) 秒,点的颜色状压为 \(s\),是否可行

复杂度 \(O(qn^2 2^n)\),然鹅跑不满(

T4

没看题(

正解是(复制的,防丢):

点击查看

首先考虑 \(c=0\) 的时候怎么做,\(mex\) 尽量大并且权值是一个排列,换句话说就是从权值为 \(0\) 的点开始加入一个集合 \(S\),不断加入权值为 \(1,2,3…\) 的点,使得集合中的点一直在一条路径上,直到加不动为止。

那么现在的问题是,我们加入一个点 \(x\) 的时候,怎么判断加入 \(x\) 后集合内的点是否还能在同一条路径上呢?首先设这条路径起点为 \(s\),终点为 \(t\),那么第一种情况是 \(x\) 在 \(s→t\) 的路径上,第二种情况是 \(x\) 变成起点或者终点了,第三种情况就是不合法,\(x\) 无法加入集合中。

前两种情况有一个非常高明的方法求得,首先我们求出这三个点的“交叉点”——即 \(s→x\) 的路径,\(s→t\) 的路径和 \(x→t\) 的路径共同经过的那个点,可以看出交叉点唯一,那么交叉点是 \(x\),那么就是第一种情况,交叉点是 \(s\) 或 \(t\),就是第二种情况,否则就是第三种情况。

据此,我们就解决了 \(c=0\) 的情况。

然后我们再依次解决 \(c=1,2,3…\) 的情况,当 \(c\) 减少 \(1\) 的时候,其实我们只需要删掉权值为 \(c\) 的点,并且重新进行 \(c=0\) 的过程即可。
那么现在的问题就是如何在集合内删掉一个点。

我们再回看进行上述过程需要这个集合支持的操作是:删掉一个点,加入一个点,维护起点和终点。首先如果删掉了起点和终点,我们需要快速找到新的起点和终点。那么我们就可以用一个 set 来维护整条路径,点的比较权值是与起点的距离。

这样如果插入一个点(非起点)的话,求一个与起点的距离加入即可,删除也是直接删。加入或删掉起点的话,距离新的起点的距离会变化,不过是整体加减一个值,在外部维护一个 lazy 标记表示当前集合内的距离与真实值的 \(Δ\) 即可。

由于一个点最多会被加入 \(2\) 次删除 \(2\) 次,每次加删的复杂度是一个 \(log\) ⁡的(set),总复杂度 \(O(n \log n)\)

实际上这道题存在 \(O(n)\) 做法。
首先我们需要一个 \(O(n)\) 预处理,\(O(1)\) 查询的 lca 算法,这可以通过初赛学到的四毛子优化解决。
考虑 \(c=0\) 时的做法,不去维护 \(set\),而只维护起点和终点,那么加入的时间复杂度为 \(O(1)\)

但是这样我们就没有办法删除了,该怎么办呢?

使用滑动窗口维护半群信息的 \(trick\),用两个栈去模拟队列。

具体来说,假设当前的队列是 \([l,r]\),我们会选择一个分界点,以分界点为栈底往左往右建立栈,维护出每个元素到栈底这段区间对应路径的起点和终点。

每次 \(r\) 往右移就往右边的栈中压入元素,\(l\) 往右移就弹出左边的栈的元素。

如果弹栈时栈已经空了怎么办?

以右端点为栈底,从右往左建立新的栈,此时左边的栈含有队列内所有元素,而右边的栈中为空。
注意每个元素最多会进入左边的栈两次,进入右边的栈两次,所以总时间复杂度为 \(O(n)\)

总结

  • T1 应该再找找规律,其实赛时已经接近正解了
  • 认真审题!!!敢闯敢试敢为天下先(T2 不敢写+看错题,其实赛时已经推出来柿子了)
  • 不要在一道题上浪费过多时间,尽量码完暴力
posted @ 2024-11-26 17:13  whrwlx  阅读(0)  评论(0编辑  收藏  举报