noip模拟8

A 图书管理

之前考过。。。

但是我忘了咋写了,然后随便胡了个动态开点权值数上去,\(O(n^2\log n)\) 拿了 \(80\)。。。

维护一个桶,检测到进来的两个数在中位数同侧,则中位数移动,否则不移动,然后就好了?。。。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1e4+4;
int mx=1e4;
int a[N];
bitset<N>vis;
signed main()
{
	freopen("book.in","r",stdin);
	freopen("book.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;long long ans=0;
	for(int i=1;i<=n;i++)cin>>a[i],ans+=(1ll*i*i*a[i]);
	for(int i=1;i<=n;i++)
	{
		int now=a[i];
		vis.reset();
		vis[a[i]]=1;
		for(int j=i+2;j<=n;j+=2)
		{
			int a1=a[j-1],a2=a[j];
			if(a1>a2)swap(a1,a2);
			vis[a1]=1,vis[a2]=1;
			if(a1<now&&a2>now);
			else if(a1<now&&a2<now) 
			{
				--now;
				while(!vis[now])now--;
			}
			else if(a1>now&&a2>now)
			{
				++now;
				while(!vis[now])now++;
			}
			ans+=(1ll*i*j*now);
		}
	}
	cout<<ans;
	return 0;
}

哎呀我是真唐,这都忘了。。

B 两棵树

连通块数 = 剩余的点数 − 剩余的边数

贡献被拆成四个部分:点 × 点 − 边 × 点 − 点 × 边 + 边 × 边

这里以 边 × 边 为例,对于树 \(T\) 的边 \((u,v)\) 假设它被保留(概率 \(\frac 1 4\)

则树 \(U\)\(u,v\) 必定被删除

计算树 \(u\) 中有多少边 \((x,y)\) 不以 \(u\)\(v\) 为端点

每条边 \((x,y)\) 都有 \(\frac 1 4\) 概率被保留

用 set 维护每个点的边,时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int Mod(int x)
{
	if(x<0)
	{
		if(x<=-mod) x%=mod;
		if(x==0) return 0;
		return x+mod;
	}
	return x>=mod?x%mod:x;
}
const int inv2=499122177,inv4=748683265,inv8=873463809,inv16=935854081;
const int maxn=2e5;
set<pair<int,int> >se;
int deg[maxn+5];
signed main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int n;cin>>n;
	int ans=Mod(Mod(n*Mod((n-1)*inv4))-Mod((n-2)*Mod((n-1)*inv4)));
	for(int i=1;i<n;i++)
	{
		int u,v;cin>>u>>v;
		if(u>v)swap(u,v);
		se.insert({u,v});
		deg[u]++,deg[v]++;
	}
	for(int i=1;i<n;i++)
	{
		int u,v;cin>>u>>v;
		if(u>v)swap(u,v);
		ans=Mod(ans+Mod((n-1-deg[u]-deg[v]+se.count({u,v}))*inv16));
	}
	cout<<ans;
	return 0;
}

C 函数(fun)

实际上 \(O(n^2)\) 可以过百万。。。

然后有一个优化可以直接干过标算,就是我们知道异或有一个性质是 \(A\text{ xor }B\text{ xor }A=B\),那我们可以开 \(map\) 存每个 \(x_i\),然后枚举 \(j=a\text{ xor }x\),找到 \(j\in{[0,B]}\) 的所有,去反过来找 \(j\text{ xor }a=x\)\(map\) 里面有没有,如果有,那看它的上一位或者下一位有没有大于 \(B\) 的,因为这样找到的 \(j\) 一定小于 \(B\),那两个函数就是异号,乘起来小于等于 \(0\)

否则如果 \(B>n\),直接跑暴力得了,因为枚举次数小于 \(B\) 的。

然后就过了,如果手写哈希表能更快。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
const int N=1e6+5;
int x[N];
int a,b;
inline int f(int i)
{
	return (i^a)-b;
}
unordered_map<int,int>mp;
signed main()
{
//	freopen("C.in","r",stdin);
//	freopen("C1.out","w",stdout);
	freopen("fun.in","r",stdin);
	freopen("fun.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>x[i],mp[x[i]]=i;
	while(q--)
	{
		cin>>a>>b;
		int ans=0;
		if(b>n)
		{
			for(int i=n-1;i>=1;i--)
			{
				if(f(x[i])*f(x[i+1])<=0){ans=i;break;}
			}
		}
		else
		{
			for(int i=b;i>=0;i--)
			{
				int now=mp[i^a];
				if(!now)continue;
				if(now<n&&(x[now+1]^a)>=b)
				{
					ans=now;break;
				}
				if(now>1&&(x[now-1]^a)>=b)
				{
					ans=now-1;break;
				}
			}
		}
		ans=ans>0?ans:-1;
		cout<<ans<<"\n";
	}
	return 0;
}

正解是这样的:

30pts

\(O(n^2)\) 枚举所有情况。

60pts

通过整体二分,判定 \([1,mid]\) 内是否有解的方式找到第一组解,或利用离线数据结构技巧进行求解,复杂度为 \(O(n \log n \log C)\),与正解关联性不大。

100pts

求解 \(x_i \oplus a\) 最小和最大的两个位置 \(c_1,c_2\),如果 \(f(c_1) \times f(c_2) > 0\) 显然无解。

否则每次取 \(c_1,c_2\) 中点 \(mid\),因为 \(f(c_1),f(c_2)\) 异号,所以 \(f(c_1),f(mid)\)\(f(mid),f(c_2)\) 必然有一对异号,每次区间长度减半,因此重复 \(\log\) 次即可。

求解 \(x_i \oplus a\) 最小和最大的两个位置 \(c_1,c_2\) 可以利用 trie 快速求解,时间复杂度为 \(((n+q) (\log n + \log V))\)

D 编辑(edit.cpp)

全输出零有 60,望周知。

30pts

枚举 \(T\) 的某一子串进行编辑距离求解的DP,具体状态为让 \(A\) 变成 \(B\),现在只考虑 \(A[1:i]\) 变成 \(B[1:j]\) 的编辑距离为 \(f[i][j]\),转移时考虑删除,添加,修改第 \(i+1\) 个位置即可,时间复杂度为 \(O(n^4)\)​。

100pts

枚举每个后缀,\(f_{i,j}\) 表示最大的 \(x\),使得 \(S[1:x]\)\(T[1:x+j]\) 可以在 \(i\) 次编辑内得到,显然若 \(x\) 可以,所有\(x_0 \leq x\)\(S[1:x_0]\)\(T[1:x_0+j]\) 都可以在 \(i\) 次编辑内得到。

考虑如何转移,先考虑做完第 \(j\) 次编辑操作后往外延申,可以延申的即为 \(S\) 的一个后缀和 \(T\) 的一个后缀的最长公共前缀,即\(f_{i,j} = f_{i,j} + \text{LCP}(S[f_{i,j + 1}:|S|],T [f_{i,j} + j + 1 . .: |T|])\),随后我们可以通过对\(f_{i+1,j-1},f_{i+1,j},f_{i+1,j+1}\) 三项进行转移,即考虑下一次的编辑的具体操作是删除添加还是修改。

每次要算的两个后缀的前缀下标不会差超过 \(k\),因此一共至多求 \(O(nk)\) 次 LCP,可以利用二分+ hash 的方式解决。

记录每个后缀中 \(f_{i,j}=|S|\) 的那些状态,即可计算出最终答案,时间复杂度为 \(O(nk^2+nk \log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned long long
const int N=5e4+5,M=35;
const ull p=13331;
int k,s1,t1,ans[M],f[M][M<<1];
ull pp[N],h1[N],h2[N];
char s[N],t[N];
inline ull gt1(int l,int r)
{
	return h1[r]-h1[l-1]*pp[r-l+1];
}
inline ull gt2(int l,int r)
{
	return h2[r]-h2[l-1]*pp[r-l+1];
}
bool ck(int lx,int rx,int ly,int ry)
{
	return gt1(lx,rx)==gt2(ly,ry);
}
int getlcp(int x,int y)
{
	int l=1,r=min(s1-x+1,t1-y+1),mid;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(ck(x,x+mid-1,y,y+mid-1)) l=mid+1;
		else r=mid-1;
	}
	return l-1;
}
void solve(int l)
{
	for(int i=0;i<=k;i++)
	for(int j=0;j<=2*30;j++) f[i][j]=-1;
	f[0][30]=0;
	for(int i=0;i<=k;i++)
	{
		for(int j=-i;j<=i;j++)
		{
			if(f[i][30+j]==-1)continue;
			f[i][30+j]=f[i][30+j]+getlcp(f[i][30+j]+1,l-1+f[i][30+j]+j+1);
			f[i+1][30+j-1]=max(f[i+1][30+j-1],f[i][30+j]+1);
			f[i+1][30+j]=max(f[i+1][30+j],f[i][30+j]+1);
			f[i+1][30+j+1]=max(f[i+1][30+j+1],f[i][30+j]);
		}
	}
	for(int j=-k;j<=k;j++)
	{
		for(int i=0;i<=k;i++)
		{
			if(s1+j>=1&&s1+j<=t1-l+1&&f[i][30+j]>=s1)
			{
				++ans[i];break;
			}
		}
	}
}
signed main()
{
	freopen("edit.in","r",stdin);
	freopen("edit.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>k;
	cin>>(s+1)>>(t+1);
	s1=strlen(s+1),t1=strlen(t+1);
	pp[0]=1;
	for(int i=1;i<=s1;i++)
	{
		h1[i]=h1[i-1]*p+s[i]-'a';
		pp[i]=pp[i-1]*p;
	}
	for(int i=1;i<=t1;i++)
	{
		h2[i]=h2[i-1]*p+t[i]-'a';
		pp[i]=pp[i-1]*p;
	}
	for(int i=1;i<=t1;i++) solve(i);
	for(int i=0;i<=k;i++) cout<<ans[i]<<"\n";
}
posted @ 2024-11-07 16:29  ccjjxx  阅读(39)  评论(7编辑  收藏  举报