noip模拟22

今天是用 2020 年的 NOIP 模拟的。T2 被换掉了,因为之前专题做过,然后换了个更好做的?

也是有了自己的 200+。

ps:没有按难度排序。

A [NOIP2020] 微信步数

B [NOIP2020] 移球游戏

神秘构造题,但其实思路很简单。

考虑一种接口化,或者说模块化的解法,使得我能利用一些柱子实现某一个数只出现在某一个柱子上。

打个比方,这就像魔方一样,可以构造一个特定的公式,使得一些数交换位置而其他数不受影响。

Step 1 制造全 \(0\)

首先,假设当前要处理的元素是 now,那我们就把所有 now 标记成 \(1\),其他标记成 \(0\)

然后考虑一个事情,如何把一个序列的 \(0\)\(1\) 分开?

记第 \(i\) 根柱子上的 \(i\) 的个数为 \(tot_i\),我们可以先把第 \(n\) 个柱子上的前 \(tot_i\) 个移动到 \(n+1\)(空柱子)上

image

然后将第一列的 \(1\) 挪到第 \(n\) 列,其他 \(0\) 挪到第 \(n+1\) 列。

image

将第 \(n+1\) 列的前 \(m-tot_i\)\(0\) 挪回第 \(1 列\)

image

然后,将第 \(2\) 列的 \(0\) 分裂到第 \(1\) 列和第 \(n+1\) 列。

image

然后就会发现第 \(2\) 列没有数了,第 \(1\) 列成了全 \(0\) 列。

注意到原本定义的列数发生错位了,我们可以用 \(p_i\) 标记 \(i\) 列的原始编号。这样在进行完上述操作之后就需要交换 \(1\)\(n\) 的编号和 \(2\)\(n+1\) 的编号。

Step 2 构造全 \(1\)

我们在上述操作中已经构造出来一个全 \(0\) 的序列,并已经使得 \(1\) 号柱子上的全部 \(1\) 都到了 \(n\) 号柱子的最上方。重复以上过程就会使全部的 \(1\) 出现在所有序列上方,把它放到那个空序列就行。

处理 \(n=2\) 的情况

其实很简单,和上述思路类似,把前 \(tot\) 的放在 \(3\) 号,把 \(1\) 号柱子分裂在 \(2\) 号和 \(3\) 号上,再把分裂后的回填回 \(1\),这样 \(1\) 柱子就上下分层了,把 \(3\) 填到 \(2\) 上,就好了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=401;
int stk[51][N],top[51];
int tot[51];
vector<pair<int,int> >ans;
int p[51];
//
//void calc(int col,int a,int b,int c,int d)
//{
//	for(int i=1;i<=tot[p[a]];i++)
//	{
//		
//	}
//}
inline void move(int a,int b)
{
	ans.push_back({a,b});
	stk[b][++top[b]]=stk[a][top[a]--];
}
int m;
inline int tp(int a)
{
	return stk[a][top[a]];
}
signed main()
{
	freopen("ball.in","r",stdin);
	freopen("ball.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		p[i]=i;
		for(int j=1;j<=m;j++) cin>>stk[i][j];
		top[i]=m;
	}
	p[n+1]=n+1;
	top[n+1]=0;
	for(int now=n;now>=3;now--)
	{
		int tmp=0;
		for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==now);
		for(int i=1;i<=tmp;i++)move(p[now],p[now+1]);
		for(int i=1;i<=m;i++)
		{
			if(tp(p[1])==now) move(p[1],p[now]);
			else move(p[1],p[now+1]);
		}
		for(int i=1;i<=m-tmp;i++) move(p[now+1],p[1]);
		for(int i=1;i<=m;i++)
		{
			if(tp(p[2])==now||top[p[1]]==m) move(p[2],p[now+1]);
			else move(p[2],p[1]);
		}
		swap(p[1],p[now]),swap(p[2],p[now+1]);
		for(int k=1;k<now;k++)
		{
			tmp=0;
			for(int j=1;j<=m;j++)tmp+=(stk[p[k]][j]==now);
			for(int i=1;i<=tmp;i++)move(p[now],p[now+1]);
			for(int i=1;i<=m;i++)
			{
				if(tp(p[k])==now) move(p[k],p[now]);
				else move(p[k],p[now+1]);
			}
			swap(p[k],p[now+1]);
			swap(p[k],p[now]);
		}
		for(int i=1;i<now;i++) while(tp(p[i])==now) move(p[i],p[now+1]);
		for(int i=1;i<now;i++) while(top[p[i]]<m) move(p[now],p[i]);
	}
//	cerr<<ans.size()<<"\n";
	int tmp=0;
	for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==1);
	for(int i=1;i<=tmp;i++)move(p[2],p[3]);
	for(int i=1;i<=m;i++)
	{
		if(tp(p[1])==1) move(p[1],p[2]);
		else move(p[1],p[3]);
	}
	for(int i=1;i<=tmp;i++) move(p[2],p[1]);
	for(int i=1;i<=m-tmp;i++) move(p[3],p[1]);
	while(top[p[3]]) move(p[3],p[2]);
	while(tp(p[1])==2) move(p[1],p[3]);
	for(int i=1;i<=m;i++)
	{
		if(tp(p[2])==1) move(p[2],p[1]);
		else move(p[2],p[3]);
	}
	cout<<ans.size()<<"\n";
	for(auto i:ans) cout<<i.first<<" "<<i.second<<"\n";
}

C [NOIP2020] 排水系统

这才是真正的 T1。开考半个小时切了。

按题意模拟即可。但是会爆 long long,这给我提了个醒,当大样例答案特别大,但是没超 long long 的时候,一定要自己再搓一个更大的样例,看看会不会超范围。如果超了果断换 int128 或者写高精。其实有个更冒险的做法是用 long double,虽然存储范围很大,但是越大精度越低。

所以还是用 int128 比较好。

后记:题目没卡深搜,其实最正确的做法是拓扑排序,遇到入度为 \(0\) 的点就放水,直到最后。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int __int128
int n,m;
const int N=1e5+5;
vector<int>e[N];
int indeg[N],outdeg[N];
int ansa[N],ansb[N];
int lcm(int x,int y)
{
	if(x==0||y==0) return 1;
	return x/__gcd(x,y)*y;
}
inline void add(int u,int a,int b)
{
	int lc=lcm(ansb[u],b);
	ansa[u]=ansa[u]*(lc/ansb[u]);
	ansb[u]=lc;
	a=a*(lc/b);
	ansa[u]+=a;
	int gc=__gcd(ansa[u],ansb[u]);
	ansa[u]/=gc,ansb[u]/=gc;
}
void dfs(int u,int a,int b)
{
	for(int v:e[u])
	{
		if(!outdeg[v])
		add(v,a,b*outdeg[u]);
		dfs(v,a,b*outdeg[u]);
	}
}
inline int read()
{
	register int s=0;
	register char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar();
	return s;
}
void write(int x)
{
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
signed main()
{
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		ansb[i]=1;
		int d=read();
		for(int j=1;j<=d;j++)
		{
			int v=read();
			outdeg[i]++,indeg[v]++;
			e[i].push_back(v);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(indeg[i]==0) dfs(i,1,1);
	}
	for(int i=1;i<=n;i++)
	{
		if(!outdeg[i]) write(ansa[i]),putchar(' '),write(ansb[i]),putchar(10); 
	}
}

D 三元组

\(O(n^3)\) 的暴力是好想的,\(O(nk)\) 的性质是好做的。

merge 起来就有 \(60\) 分。

\(dp[i][j]\) 表示到第 \(i\) 个数时 \(a+b^b\)(其中 \(a\in[1,i]b\in[a,i]\))余数为 \(k\) 的数量。因为每新加入一个 \(i\) 就相当于新加入点对 \((1,i),(2,i)\cdots (i,i)\),也就是所有 \(i^2+1\)\(i^2+i\)\(k\) 余数的个数加一。\(dp[i][j]=dp[i-1][j]+b[j]\),其中 \(b[j]\) 表示新加入点对中余数为 \(j\) 的个数。答案就是 \(\displaystyle \sum_{i=1}^n dp[i][i^3 \text{ mod }k]\)

可以滚一下,降低空间复杂度。

然后发现可以拿树状数组维护值域区间 \([0,k)\),就是每次在上述区间上区间加 \(1\),并给重复的整块加上 \(\lfloor \frac i k\rfloor\),用区修单查树状数组就实现了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,k;
int *c;
struct SegTree{
	inline int lowbit(int x)
	{
		return x&-x;
	}
	void update(int x,int K)
	{
		x++;
		for(;x<=k;x+=lowbit(x)) c[x]+=K;
	}
	void Update(int l,int r,int K)
	{
		update(l,K),update(r+1,-K);
	}
	int query(int x)
	{
		x++;int res=0;
		for(;x;x-=lowbit(x)) res+=c[x];
		return res;
	}
}st;
void q()
{
	cin>>n>>k;
	c=new int [k+10];
	for(int i=0;i<=k+9;i++) c[i]=0;
	int ans=0,tmp=0;
	for(int i=1;i<=n;i++)
	{
		int l=(i*i+1),r=i*(i+1);
		l%=k,r%=k;
		tmp+=i/k;
		if(i%k)
		{
			if(l>r) st.Update(0,r,1),st.Update(l,k-1,1);
			else st.Update(l,r,1);
		}
		ans+=st.query((i*i%k*i)%k)+tmp;
	}
	cout<<ans<<"\n";
}
signed main()
{
	freopen("exclaim.in","r",stdin);
	freopen("exclaim.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int T;cin>>T;
	for(int i=1;i<=T;i++) cout<<"Case "<<i<<": ",q();
}
posted @ 2024-11-27 20:39  ccjjxx  阅读(11)  评论(0编辑  收藏  举报