2019 Multi-University Training Contest 1(1002|1004|1005|1009|1011)

本文分享了一场北邮自闭场的算法竞赛心得,详细解析了包括可持久化异或线性基、二分搜索、最短路径最小割、贪心算法及线性筛等高级数据结构和算法的应用技巧。

心得

北邮出的一场自闭场,所幸发了数据题解和标程

然而题目难度告诉我们,即使发了这些,也有一半没学过

1002.(hdu6579)Operation(可持久化异或线性基)

n(n<=1e5)个数,m(m<=1e5)次操作,操作分两种

0 l r 询问[l,r]内取出一些数异或的最大值

1 x 将x插入n个数的尾部,同时令n+1

题目强制在线

思路来源:https://www.jianshu.com/p/866143c93409

就觉得应该有可持久化异或线性基,搜了一下板子,就过了

可持久化异或线性基原理:

对于每个线性基,将出现位置靠右的数字尽可能地放在高位,

在插入新数字的时候,要同时记录对应位置上数字的出现位置,

并且在找到可以插入的位置的时候,如果新数字比位置上原来的数字更靠右,就将该位置上原来的数字向低位推。

在求最大值的时候,从高位向低位遍历,

如果该位上的数字出现在询问中区间左端点l的右侧且可以使答案变大,就异或到答案里。

#include<bits/stdc++.h>
using namespace std;
struct PersistentXorBasis
{
	#define sz(b) b.size()
	#define fi first
	#define se second
	#define pb push_back
    static const int __=1e6+5;
    typedef int type;
    struct XorBasis
    {
        vector<pair<type,int> >G;
        int zero;   //最后出现的位置

        XorBasis() {clear();}

        void operator=(const XorBasis &b)
        {
            for(int i=0;i<sz(b.G);++i)
                G.push_back(b.G[i]);
            zero=b.zero;
        }

        int size() {return G.size()+bool(zero);}

        void clear()
        {
            zero=0;
            G.clear();
        }
    }X[__];

    int n;

    void insert(pair<type,int>x)
    {
        vector<pair<type,int> >&G=X[x.se].G;
        for(int i=sz(G)-1;~i && x.fi;--i)
        {
            type t=x.fi^G[i].fi;
            if(t<x.fi)
            {
                if(t>G[i].fi)break;
                if(x.se>G[i].se)swap(G[i],x);
                x.fi^=G[i].fi;
            }
        }
        if(!x.fi){X[x.se].zero=x.se;return;}
        G.pb(x);
        for(int i=sz(G)-1;i && G[i].fi<G[i-1].fi;--i)
            swap(G[i],G[i-1]);
    }

    void build(type a[],int _n)
    {
        n=_n;
        for(int i=1;i<=n;++i)
        {
            X[i]=X[i-1];
            insert(make_pair(a[i],i));
        }
    }

    type get_max(int l,int r)
    {
        vector<pair<type,int> >&G=X[r].G;

        type res=0;
        for(int i=sz(G)-1;~i;--i)
            if(G[i].se>=l && (res^G[i].fi)>res)
                res^=G[i].fi;
        return res;
    }

    void clear()
    {
        for(int i=1;i<=n;++i)
            X[i].clear();
    }
}X;
int t,n,m,v;
int op,l,r,lastans;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		X.n=n;
		for(int i=1;i<=X.n;++i)
		{
			scanf("%d",&v);
			X.X[i]=X.X[i-1];
			X.insert(make_pair(v,i));
		}
		lastans=0;
		for(int i=1;i<=m;++i)
		{
			scanf("%d",&op);
			if(op==0)
			{
				scanf("%d%d",&l,&r);
				l=(l^lastans)%X.n+1;
				r=(r^lastans)%X.n+1;
				if(l>r)swap(l,r);
				printf("%d\n",lastans=X.get_max(l,r));
			}
			else if(op==1)
			{
				X.n++;
				X.X[X.n]=X.X[X.n-1];
				scanf("%d",&v);
				v=v^lastans;
				X.insert(make_pair(v,X.n));
			}
		}
		X.clear();
	}
	return 0;
}

写个简短的版本,用两个数组实现的

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int lg=31;
int base[N][lg],pos[N][lg];
int t,n,m,v;
int op,l,r,lastans;
void ins(int i,int x)//
{
	memcpy(base[i],base[i-1],sizeof base[i]);
	memcpy(pos[i],pos[i-1],sizeof pos[i]);
	int k=i;
	for(int j=30;j>=0;--j)
	{
		if(!(x>>j&1))continue; 
		if(!base[i][j])
		{
			base[i][j]=x;
			pos[i][j]=k;
			break;
		}
		else
		//把等大的线性基 换成后面这个 把x异或变小 再向低位找 
		//否则直接向低位找 
		{
			if(k>pos[i][j])
			{
				swap(base[i][j],x);
				swap(pos[i][j],k);
			}
			x^=base[i][j];
		}
	}
}
int ask(int l,int r)
{
	int res=0;
	for(int j=30;j>=0;--j)
	{
		if(pos[r][j]>=l)//有基的pos的值了 就代表有基 
		res=max(res,res^base[r][j]);
	}
	return res;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&v);
			ins(i,v);
		}
		lastans=0;
		for(int i=1;i<=m;++i)
		{
			scanf("%d",&op);
			if(op==0)
			{
				scanf("%d%d",&l,&r);
				l=(l^lastans)%n+1;
				r=(r^lastans)%n+1;
				if(l>r)swap(l,r);
				printf("%d\n",lastans=ask(l,r));
			}
			else if(op==1)
			{
				n++;
				scanf("%d",&v);
				v=v^lastans;
				ins(n,v);
			}
		}
		for(int i=1;i<=n;++i)
		{
			memset(base[i],0,sizeof base[i]);
			memset(pos[i],0,sizeof pos[i]);
		}
	}
	return 0;
}

1004.(hdu6581)Vacation(二分)

数轴上x轴正方向有n(n<=1e5)辆车,第i辆的车头位于原点右侧si,长度为li,以vi速度满速向左开(1<=si,li,vi<=1e9)

当追上左侧车辆时,无缝衔接保持前车速度跟车,问最右边的车的车头碰到原点时的时间,

保证输入按si降序输入,即从右到左输入车的位置

 

二分时间t,考虑t时间内右车能到的数轴位置,

如果在左车的车尾的右侧就为左车车尾,否则为右车原位置-这段时间实际开的距离

答案为最小的让最右边的车到达原点的t

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
typedef long long ll;
const double eps=1e-8;
const int N=1e5+10;
int n;
double l,r;
struct node
{
	double len,pos,v;
}e[N],tmp[N];
bool ok(double mid)
{
	for(int i=0;i<=n;++i)
	tmp[i]=e[i];
	tmp[0].pos=tmp[0].pos-mid*tmp[0].v;
	for(int i=1;i<=n;++i)
	tmp[i].pos=max(tmp[i].pos-mid*tmp[i].v,tmp[i-1].pos+tmp[i-1].len);
	return tmp[n].pos<=eps;
}
int main()
{
	while(~scanf("%d",&n))
	{
		for(int i=0;i<=n;++i)
		scanf("%lf",&e[i].len);
		for(int i=0;i<=n;++i)
		scanf("%lf",&e[i].pos);
		for(int i=0;i<=n;++i)
		scanf("%lf",&e[i].v);
		reverse(e,e+n+1);
		l=0;r=1e14+1;
		while(r-l>eps)
		{
			double mid=(l+r)/2;
			if(ok(mid))r=mid;
			else l=mid;
		}
		printf("%.7lf\n",l);
	}
    return 0;
}

1005.(hdu6582)Path

n(n<=1e4)个点,m(m<=1e4)条单向边,第i条边u->v有权值w(w<=1e9)

求删掉的边的最小权值和,使得原图中的最短路大于没删边之前的最短路

 

显然只有删最短路上的边才有意义,所以要先处理出最短路的图

先跑一遍最短路,求得距离,满足dis[v]==dis[u]+w的边是在最短路径图上的

用这些边新建一个图,跑最小割,即为答案,

Dinic复杂度似乎在这里玄学了一次,弧优化就完事了,bzoj1266原题警告

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map> 
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3fll;
const int maxn=1e4+10;
const int maxm=1e4+10;
int level[maxn];
int head[maxn],cnt,num;
int t,n,m,u,v;
ll w;
int ss,ee;
bool vis[maxn];
ll dis[maxn];
struct edg{int u,v;ll w;}tmp[maxm];
struct edge{int v,nex;ll w;}e[maxm];
priority_queue<pii,vector<pii>,greater<pii> >q;
void add(int u,int v,ll w)
{
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt++;
}
bool bfs(int s,int t)
{
	queue<int>q;
	memset(level,0,sizeof level);
	level[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		if(x==t)return 1;
		for(int u=head[x];~u;u=e[u].nex)
		{
			int v=e[u].v;ll w=e[u].w;
			if(!level[v]&&w)
			{
				level[v]=level[x]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
ll dfs(int u,ll maxf,int t)
{
	if(u==t)return maxf;
	ll ret=0;
	for(int i=head[u];~i;i=e[i].nex)
	{
		int v=e[i].v;ll w=e[i].w;
		if(level[u]+1==level[v]&&w)
		{
			ll MIN=min(maxf-ret,w);
			w=dfs(v,MIN,t);
			e[i].w-=w;
			e[i^1].w+=w;
			ret+=w;
			if(ret==maxf)break;
		}
	}
	if(!ret)level[u]=-1;//优化,防止重搜,说明u这一路不可能有流量了 
	return ret;
}
ll Dinic(int s,int t)
{
	ll ans=0;
	while(bfs(s,t))
	ans+=dfs(s,INF,t);
	return ans;
}
void dijkstra(int u)
{
	while(!q.empty())q.pop(); 
	for(int i=1;i<=n;++i)
	vis[i]=0,dis[i]=1e18;
	dis[u]=0;
	q.push(pii(dis[u],u));
	while(!q.empty())
	{
		pii tmp=q.top();
		q.pop();
		int pos=tmp.second;
		ll d=tmp.first;
		if(vis[pos])continue;
		vis[pos]=1;
		for(int i=head[pos];~i;i=e[i].nex)
		{
			int v=e[i].v;
			ll w=e[i].w;
		    if(dis[v]>dis[pos]+w)
		    {
		    	dis[v]=dis[pos]+w;
		    	q.push(pii(dis[v],v));
		    }
		}
    }
}
void dfs2(int u) 
{
	vis[u]=1;
	for(int i=head[u];~i;i=e[i].nex)
	{
		int v=e[i].v;
		ll w=e[i].w;
		if(dis[v]==dis[u]+w)
		tmp[num++]=edg{u,v,w};
		if(vis[v])continue;
		dfs2(v);
	}
}
int main()
{ 
    scanf("%d",&t);
    //n个点 m条边 
    while(t--)
    {
    	cnt=0;num=0;
		memset(head,-1,sizeof head);
    	scanf("%d%d",&n,&m);
    	ss=1;ee=n;
    	for(int j=0;j<m;++j)
    	{
    		scanf("%d%d%lld",&u,&v,&w);
    		add(u,v,w);
    	}
    	dijkstra(ss);
    	memset(vis,0,sizeof vis);
    	dfs2(ss);
    	cnt=0;
		memset(head,-1,sizeof head);
		for(int i=0;i<num;++i)
		{
			add(tmp[i].u,tmp[i].v,tmp[i].w);
			add(tmp[i].v,tmp[i].u,0);
		}
    	ll ans=Dinic(ss,ee);
    	printf("%lld\n",ans); 
    }
	return 0;
}

1009.(hdu6586)String(贪心)

串长不超过1e5的全小写字母构成的字符串|s|,要求构造它的一个长度为k的子序列,

满足第i个小写字母的个数在[L[i],R[i]]之间

 

倒序预处理每个位置每个字母还有多少个,每个字母开vector存同字母的位置

正序填入k个字母,每次从小到大遍历字母j,试在上一次填的位置last后填这一位j,

如果j无剩余或已填到上限,放弃,否则试填j,填完之后检查j,

若后续所有剩余j,加上已填j,仍不够下限,回滚

若把所有字母填到上限,也填不满k位,回滚

若把所有字母都只填到下限,也超过k位,回滚

否则,说明这一位合法,填上即可,有点银行家算法的意思啊……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e5+10; 
int k,n,v;
int now[N][26],l[26],r[26],used[26];
vector<int>pos[26];
vector<int>::iterator head[26]; 
int last;
char s[N],ans[N];
bool ok;
int main()
{
	while(~scanf("%s%d",s,&k))
	{
		for(int j=0;j<26;++j)
		scanf("%d%d",&l[j],&r[j]);
		n=strlen(s);
		memset(now[n],0,sizeof now[n]);
		for(int i=n-1;i>=0;--i)
		{
			memcpy(now[i],now[i+1],26*sizeof(int));
			v=s[i]-'a';
			now[i][v]++;
		}
		for(int j=0;j<26;++j)
		used[j]=0,pos[j].clear();
		for(int i=0;i<n;++i)
		pos[s[i]-'a'].push_back(i);
		for(int j=0;j<26;++j)
		head[j]=pos[j].begin();
		ok=1;last=-1;
		for(int i=0;i<k&&ok;++i)
		{
			bool upd=0; 
			for(int j=0;j<26;++j)
			{
				if(used[j]==r[j])continue; 
				while(head[j]!=pos[j].end()&&(*head[j])<=last)head[j]++;
				if(head[j]==pos[j].end())continue;
				bool flag=1;
				used[j]++;
				int sum=0,to=*head[j];
				for(int j=0;j<26;++j)
				{
					if(now[to+1][j]+used[j]<l[j])flag=0;//该字符选不够
					sum+=max(l[j]-used[j],0);//最少还要选的字符 
				}
				if(sum>k-1-i)flag=0;//还要选的字符比当前剩余位置多 
				sum=0; 
				for(int j=0;j<26;++j)
				{
					sum+=min(r[j]-used[j],now[to+1][j]);//还能选的字符 
				}
				if(sum<k-1-i)flag=0;//还能选的字符比当前剩余位置少 
				if(!flag)used[j]--;
				else
				{
					ans[i]=j+'a';
					last=to;
					upd=1;
					break;
				}
			}
			if(!upd)ok=0;
		}
		if(ok)
		{
			ans[k]='\0';
			printf("%s\n",ans);
		}
		else puts("-1");
	}
	return 0;
}

1011.(hdu6588)Function(线性筛)

,1<=n<=1e21

把枚举i变成枚举j=\sqrt[3]{i},从而i=j^{3}

对于每个j,i从j^{3}(j+1)^{3}-1,但是最后一个j可能由于n的限制取不满,所以分成两部分

注意枚举约数和枚举倍数的转化,T=d*t,

还有就是[gcd(i,a)==d]=[gcd(\frac{i}{d},\frac{a}{d})==1]=e(\frac{i}{d},\frac{a}{d})=\sum _{t|\frac{i}{d},t|\frac{a}{d}}\mu (t)

这种题还是多练试推吧,不一定要按题解那么列,但也要搞出来啊

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map> 
#include<algorithm>
using namespace std;
const __int128 h=100;
const int maxn=1e7+10;
const int mod=998244353;
typedef long long ll;
bool ok[maxn];
int prime[maxn];
int phi[maxn],square[maxn],sigma[maxn],cnt;
int inv6,inv2;
int modpow(int x,int n,int mod)
{
	int res=1;
	for(;n;n>>=1,x=1ll*x*x%mod)
	if(n&1)res=1ll*res*x%mod;
	return res;
}
void sieve()
{ 
    phi[1]=1;
	for(int i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(1ll*i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
	inv2=modpow(2,mod-2,mod);
	inv6=modpow(6,mod-2,mod);
	for(int w=1;w<maxn;++w)
	{
		int tmp=1ll*3*w*(w+1)%mod;
		sigma[w]=1ll*tmp*inv2%mod;
		square[w]=1ll*tmp*(2*w+1)%mod*inv6%mod;
	}
}
template <class T>
void read(T &x)
{
	static char ch;static bool neg;
	for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
	for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
	x=neg?-x:x;
}
int t;
__int128 n,u,v;
int solve1()
{
	int ans=0,up;
	for(int i=1;i*i*i<=n;++i)
	{
		up=min((int)n,(i+1)*(i+1)*(i+1)-1);
		for(int j=i*i*i;j<=up;++j)
		{
			ans=ans+__gcd(i,j);
			if(ans>=mod)ans-=mod;
		}
	}
	return ans;
}
int cal(__int128 d,__int128 up)//sigma i从1到up gcd(i,d) 
{
	int ans=0;
	for(int i=1;i*i<=d;++i)
	if(d%i==0)
	{
		ans=(ans+up/i%mod*phi[i]%mod)%mod;
		if(d/i!=i)ans=(ans+up/(d/i)%mod*phi[d/i]%mod)%mod;
	}
	return ans;
}
int solve2()
{
	__int128 l=0,r=1e7+10;
	while(l<=r)
	{
		__int128 m=(l+r)/2;
		if(m*m*m>n)r=m-1;
		else l=m+1;
	}
	u=r;v=r-1;
	int ans=(cal(u,n)-cal(u,u*u*u-1)+mod)%mod;
	for(int i=1;i<=v;++i)
	{
		int w=v/i;//倍数上界 
		ans=(ans+(1ll*i*square[w]%mod+sigma[w]+w)*phi[i]%mod)%mod;
	}
	return ans;
}
int main()
{
	sieve();
	read(t);
	while(t--)
	{
		read(n);
		if(n<h)printf("%d\n",solve1());
		else printf("%d\n",solve2());
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值