noip36

开场先看一遍题面,凭着错误的感觉t3叫naive是一个原因,312开局。然后就死的很惨。

T1

朴素暴力40pts,细想就有80pts,然而我只写了十分钟左右就爬回T3了,所以...

其实都是借口

正解:

转换一下题意,给定n个非负整数的可重集合,要求满足 \(\sum_{i=1}^{n}x_{i}=m\) ,那么一定有 \(diff\{x_{n}\}\le\sqrt{m}\) ,其中 \(diff\) 表示本质不同的数。

所以用并查集维护一下牌堆之间的关系,用树状数组统计个数即可。

Code
#include<cstdio>
#define MAX 100010
#define re register
#define int long long
namespace OMA
{
	int n,m,num;
	int w[MAX],p[MAX],cnt[MAX];
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			int w=1; s=0; char ch=getchar();
			while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
			while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
			return s*=w,*this;
		}
	}cin;
	struct BIT
	{
		int tree[MAX];
		inline int lowbit(int x)
		{ return x&-x; }
		inline void update(int x,int y)
		{
			for(re int i=x; i<=n; i+=lowbit(i))
			{ tree[i] += y; }
		}
		inline int query(int x)
		{
			int res = 0;
			for(re int i=x; i; i-=lowbit(i))
			{ res += tree[i]; }
			return res;
		}
	}BIT;
	inline void modify(int val)
	{
		for(re int i=p[val]; i<=num-1; i++)
		{ w[i] = w[i+1],p[w[i]] = i; }
		num--,p[val] = 0;
	}
	int fa[MAX],size[MAX];
	inline int find(int x)
	{ return x != fa[x]?fa[x] = find(fa[x]):fa[x]; }
	inline void merge(int x,int y)
	{
		int r1 = find(x),r2 = find(y);
		if(r1!=r2)
		{
			fa[r2] = r1;
			BIT.update(size[r1],-1);
			BIT.update(size[r2],-1);
			if(--cnt[size[r1]]==0)
			{ modify(size[r1]); }
			if(--cnt[size[r2]]==0)
			{ modify(size[r2]); }
			size[r1] += size[r2];
			BIT.update(size[r1],1);
			if(++cnt[size[r1]]==1)
			{ w[++num] = size[r1],p[size[r1]] = num; }
		}
	}
	signed main()
	{
		cin >> n >> m;
		for(int i=1; i<=n; i++)
		{ fa[i] = i; size[i] = 1; }
		BIT.update(1,n);
		w[num = 1] = 1,cnt[1] = n;
		for(re int i=1,opt; i<=m; i++)
		{
			cin >> opt;
			if(opt==1)
			{
				int x,y;
				cin >> x >> y;
				merge(x,y);
			}
			else
			{
				int c,ans = 0; cin >> c;
				int N = BIT.query(n);
				//printf("N=%lld\n",N);
				//printf("%lld %lld\n",num,w[num]);
				for(re int j=1; j<=num; j++)
				{
					//printf("j=%lld\n",j);
					if(w[j]+c-1>=n)
					{ continue ; /*printf("QAQ %lld\n",c);*/ }
					//printf("QAQ %lld ",w[j]);
					ans += cnt[w[j]]*(N-BIT.query(w[j]+c-1));
					if(!c)
					{ ans -= cnt[w[j]]*cnt[w[j]]-(cnt[w[j]]-1)*cnt[w[j]]/2; }
				}
				printf("%lld\n",ans);
			}
			//printf("i=%lld\n",i);
		}
		return 0;
	}
}
signed main()
{ return OMA::main(); }

T2

咕咕咕

image
image
image

怎么tm不说人话

upd on 08-13

今天T3是个点分治,没学过的我在权衡之下,滚回来改这题。

首先可以根据归并排序的过程和原序列,来得到归并排序的指针移动的概率,设为 \(p_{i,j}\) 表示当前归并排序过程中,两个指针分别指向 \(i,j\) 的概率。

\(p\) 数组就是在模拟归并排序的过程。

然后考虑dp,设 \(dp_{dep,i,j}\) 表示当前在归并排序的第 \(dep\) 层, 由 \(i\) 转移到 \(j\) 的概率,有了上边的 \(p\) 数组,就可以直接从 \(dep+1\) 转移到 \(dep\) ,转移时根据 \(a\) 的大小分类讨论。

最后答案直接相乘累加即可。

Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510
#define re register
#define int long long
using std::sort;
namespace OMA
{
	int n,a[N];
	int p[N][N],dp[N][N][N];
	const int mod = 998244353;
	const int inv = 499122177;
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			int w=1; s=0; char ch=getchar();			
			while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
			while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
			return s*=w,*this;
		}
	}cin;
	inline void merge_sort(int dep,int l,int r)
	{
		if(l==r)
		{ dp[dep][l][r] = 1; return ; }
		int mid = (l+r)>>1;
		merge_sort(dep+1,l,mid),merge_sort(dep+1,mid+1,r);
		memset(p,0,sizeof(p)); p[0][0] = 1;
		for(re int i=0; i<=mid-l+1; i++)
		{
			for(re int j=0; j<=r-mid; j++)
			{
				if(i==mid-l+1&&j==r-mid)
				{ continue ; }
				if(i==mid-l+1)
				{ (p[i][j+1] += p[i][j]) %= mod; }
				else if(j==r-mid)
				{ (p[i+1][j] += p[i][j]) %= mod; }
				else if(a[i+l]<a[j+mid+1])
				{ (p[i+1][j] += p[i][j]) %= mod; }
				else if(a[i+l]>a[j+mid+1])
				{ (p[i][j+1] += p[i][j]) %= mod; }
				else if(a[i+l]==a[j+mid+1])
				{
					(p[i+1][j] += p[i][j]*inv%mod) %=mod;
					(p[i][j+1] += p[i][j]*inv%mod) %= mod;
				}
			}
		}
		for(re int i=l; i<=r; i++)
		{
			for(re int j=0; j<=mid-l+1; j++)
			{
				for(re int k=0; k<=r-mid; k++)
				{
					if(j==mid-l+1&&k==r-mid)
					{ continue ; }
					if(j==mid-l+1)
					{ (dp[dep][i][j+k+l] += dp[dep+1][i][k+mid+1]*p[j][k]%mod) %= mod; }
					else if(k==r-mid)
					{ (dp[dep][i][j+k+l] += dp[dep+1][i][j+l]*p[j][k]%mod) %= mod; }
					else if(a[j+l]<a[k+mid+1])
					{ (dp[dep][i][j+k+l] += dp[dep+1][i][j+l]*p[j][k]%mod) %= mod; }
					else if(a[j+l]>a[k+mid+1])
					{ (dp[dep][i][j+k+l] += dp[dep+1][i][k+mid+1]*p[j][k]%mod) %= mod; }
					else if(a[j+l]==a[k+mid+1])
					{
						(dp[dep][i][j+k+l] += dp[dep+1][i][j+l]*p[j][k]%mod*inv%mod) %= mod;
						(dp[dep][i][j+k+l] += dp[dep+1][i][k+mid+1]*p[j][k]%mod*inv%mod) %= mod;
					}
				}
			}
		}
		sort(a+l,a+r+1);
	}
	signed main()
	{
		cin >> n;
		for(re int i=1; i<=n; i++)
		{ cin >> a[i]; }
		merge_sort(1,1,n);
		for(re int i=1,ans; i<=n; i++)
		{
			ans = 0;
			for(re int j=1; j<=n; j++)
			{ (ans += j*dp[1][i][j]%mod) %= mod; }
			printf("%lld ",ans);
		}
		return 0;
	}
}
signed main()
{ return OMA::main(); }

T3

一直在写,然而分并不高。

因为一直在用错误的思路,上厕所的时候还把自己搞掉了,然而回来后,继续乱搞。

乱搞:

测试点b分治

首先写个 \(O(n^2)\) 暴力,发现 \(n\le30000\) 随便跑,然后我们大胆一些其实是经由某b试出来的,当当前算出的\(r-l+1>700\) 时,停止枚举 \(r\) ,直接去更新答案。

没有正确性,但是能A。大雾

正解:

st表+链表。

我们固定右端点,枚举左端点来考虑。

对于本题,有几个比较关键的性质:

  1. \(OR-AND\) 本质不同的位置有 \(2\log n\) 个,且本质相同的位置都是连续的
  2. \(OR-AND\) 递增
  3. \(MIN-MAX\) 递减

对于1,考虑 \(OR\) 的后缀和 \(suf_{i}\) ,对于任意一个满足 \(suf_{i}\neq sud_{i+1}\) 的位置,\(suf_{i+1}\) 相较于\(suf_{i}\) 必然是至少有一位由 0 变成了 1 。总的二进制位数为 \(log\) 级别,所以 \(OR\) 本质不同的位置共有 \(log n\) 个,\(AND\) 同理,所以 \(OR-AND\) 本质不同的位置共有 \(2log n\) 个。

实在不懂,可以手模一下,就明白了。

2,3很好理解,就不再说了。

当移动右端点时,考虑用链表来记录下左端点所有 \(OR-AND\) 不同的区段,就是用链表记录一下右端点,对于 \(OR-AND\) 相等的一段,\(MIN-MAX\) 是递减的,所以直接二分来搞,\(O(nlog^{2} n)\)

因为我们要找的是最长的,所以找到第一个合法的直接break掉即可,\(O(nlog n)\)

再用线段树维护一下答案,最后遍历叶子结点输出答案即可。

具体实现见code。

Code
#include<list>
#include<cstdio>
#define re register
#define MAX 1000100
using std::list;
int n,k,a[MAX];
struct node
{
	int p,ro,dna;
	inline int delta()
	{ return ro-dna; }
};
list<node>LIST;
inline int max(int a,int b)
{ return a>b?a:b; }
inline int min(int a,int b)
{ return a<b?a:b; }
namespace ST
{
	int log[MAX];
	int f[MAX][20],g[MAX][20]; // xam,nim
	inline void pre_work()
	{
		for(re int i=2; i<=n; i++)
		{ log[i] = log[i>>1]+1; }
		for(re int j=1; j<=log[n]; j++)
		{
			for(re int i=1; i<=n-(1<<j)+1; i++)
			{
				f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
				g[i][j] = min(g[i][j-1],g[i+(1<<(j-1))][j-1]);
			}
		}
	}
	inline int query1(int l,int r)
	{
		int gol = log[r-l+1];
		return max(f[l][gol],f[r-(1<<gol)+1][gol]);
	}
	inline int query2(int l,int r)
	{
		int gol = log[r-l+1];
		return min(g[l][gol],g[r-(1<<gol)+1][gol]);
	}
}using namespace ST;
namespace OMA
{
	struct Segment_Tree
	{
		struct TREE
		{ int l,r,len; }st[MAX<<2];
		inline int ls(int p)
		{ return p<<1; }
		inline int rs(int p)
		{ return p<<1|1; }
		inline void build(int p,int l,int r)
		{
			st[p] = (TREE){l,r,-1};
			if(l==r)
			{ return ; }
			int mid = (l+r)>>1;
			build(ls(p),l,mid),build(rs(p),mid+1,r);
		}
		inline void update(int p,int l,int r)
		{
			if(l<=st[p].l&&st[p].r<=r)
			{
				st[p].len = max(st[p].len,r-l+1);
				return ;
			}
			int mid = (st[p].l+st[p].r)>>1;
			if(l<=mid)
			{ update(ls(p),l,r); }
			if(r>mid)
			{ update(rs(p),l,r); }
		}
		inline void print(int p)
		{
			if(st[p].l==st[p].r)
			{ printf("%d ",st[p].len); return ; }
			st[ls(p)].len = max(st[ls(p)].len,st[p].len);
			st[rs(p)].len = max(st[rs(p)].len,st[p].len);
			print(ls(p)),print(rs(p));
		}
	}Tree;
	struct stream
	{
		template<typename type>inline stream &operator >>(type &s)
		{
			int w=1; s=0; char ch=getchar();
			while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
			while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
			return s*=w,*this;
		}
	}cin;
	inline bool check(list<node>::iterator it,int l,int r)
	{ /*printf("check %d %d\n",l,r);*/ return it->delta()+query2(l,r)-query1(l,r)>=k; }
	signed main()
	{
		//freopen("data.in","r",stdin);
		//freopen("my.out","w",stdout);
		cin >> n >> k;
		for(re int i=1; i<=n; i++)
		{ f[i][0] = g[i][0] = (cin >> a[i],a[i]); }
		pre_work(); Tree.build(1,1,n);
		//printf("???\n");
		for(re int i=1; i<=n; i++)
		{
			//printf("one\n");
			for(auto it=LIST.begin(); it!=LIST.end(); it++)
			{ it->ro |= a[i],it->dna &= a[i]; /*printf("??1\n");*/ }
			//printf("two\n");
			LIST.emplace_back((node){i,a[i],a[i]});
			//printf("i=%d\n",i);
			//printf("three\n");
			for(auto it1=LIST.begin(),it2=next(it1); it2!=LIST.end(); it2++)
			{
				//printf("???2\n");
				if(it1->delta()==it2->delta())
				{ LIST.erase(it1); it1 = it2; }
				else
				{ ++it1; }
			}
			//printf("four\n");
			for(auto it=LIST.begin(); it!=LIST.end(); it++)
			{
				//printf("QAQ\n");
				if(check(it,it->p,i))
				{
					//printf("five\n");
					int l = 1,r = it->p,res;
					if(it!=LIST.begin())
					{ l = prev(it)->p+1; }
					while(l<=r)
					{
						int mid = (l+r)>>1;
						if(check(it,mid,i))
						{ r = mid-1,res = mid; }
						else
						{ l = mid+1; }
					}
					Tree.update(1,res,i);
					break ;
				}
			}
			//printf("end:%d\n",i);
		}
		Tree.print(1);
		return 0;
	}
}
signed main()
{ return OMA::main(); }

反思总结:

  1. 不要硬刚一道题,时间分配要有比重。
  2. 当前思路不可做,就换一种,哪怕是乱搞。
  3. 一些能优化的地方尽量去优化,不要直接冲傻瓜暴力。

不过话说这次读题没啥问题

posted @ 2021-08-12 06:20  -OMA-  阅读(77)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end