• HNOI2018题解


    在此处输入标题

    标签(空格分隔): 未分类


    重做了一遍,本来以为很快的,结果搞了一天。。。

    寻宝游戏

    可以发现只有(&0)(|1)会对答案有影响

    那么对于每一位,我们只要知道最后一个(&1)和最后一个(|1)谁近就可以了。

    发现并不好做,我们可以把操作串也当成(01)串,如果(&=0,|=1)好像并没有什么用,于是我们令(&=1,|=0)发现这样刚好满足了我们需要的信息,设(op)为操作串,这一位串为(a),如果(op)字典序小于(a)最后会是(1),否则为(0)。((|1=01,&0=10)这就是字典序了,手玩也可以

    那么我们把原串基数排序,那么一定可以重排成(00...011...1),否则无解。如果有解那答案就是第一个(1)串代表的十进数值减掉最后一个(0)串十进制数值。注意下边界条件。

    (code)

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    typedef std::pair<int,int> P;
    #define mk std::make_pair
    #define fr first
    #define sc second
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    const int YL=1e9+7,N=1005,M=5005;
    inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
    inline int MO(const int &x){return x>=YL?x-YL:x;}
    int pw[M],s[N][M],id[2][M],rk[M],res[M];
    int main()
    {
    	int n=in(),m=in(),q=in();
    	for(int i=1;i<=n;++i)
    	{
    		static char S[M];scanf("%s",S+1);
    		for(int j=1;j<=m;++j)s[i][j]=S[j]-'0';
    	}
    	for(int i=1;i<=m;++i)id[0][i]=i;
    	int now=0;pw[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		now^=1;int cnt=0,tot=0;pw[i]=MO(pw[i-1]<<1);
    		for(int j=1;j<=m;++j)cnt+=s[i][j]^1;
    		for(int j=1;j<=m;++j)
    			if(s[i][id[now^1][j]])id[now][++cnt]=id[now^1][j];
    			else id[now][++tot]=id[now^1][j];
    	}
    	int *p=id[now];
    	for(int i=1;i<=m;++i)rk[p[i]]=i;
    	for(int i=1;i<=m;++i)
    		for(int j=1;j<=n;++j)
    			res[i]=MO(res[i]+s[j][i]*pw[j-1]);
    	res[m+1]=pw[n];p[m+1]=m+1;
    	while(q--)
    	{
    		static char S[M];scanf("%s",S+1);
    		int mx=-1,mi=m+1;
    		for(int i=1;i<=m;++i)
    			if(S[i]=='0')mx=std::max(mx,rk[i]);
    			else mi=std::min(mi,rk[i]);
    		if(mx>=mi){puts("0");continue;}
    		printf("%d
    ",MO(res[p[mi]]-res[p[mx]]+YL));
    	}
    	return 0;
    }
    
    

    转盘

    可以发现,走一圈是最优的。

    那么即求(min(max(T_j-j)+i)+n-1)

    楼房重建即可

    (code)

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    typedef std::pair<int,int> P;
    #define mk std::make_pair
    #define fr first
    #define sc second
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    const int N=2e5+5;
    int mis[N<<2],mip[N<<2],mxv[N<<2],a[N];
    #define lc k<<1
    #define rc k<<1|1
    #define ls l, mid ,lc
    #define rs mid+1,r,rc
    #define mid ((l+r)>>1)
    int calc(int mh,int l,int r,int k)
    {
    	if(l==r)return std::max(mh,a[l])+l;int w=mxv[rc];
    	if(mh>=w)return std::min(calc(mh,ls),mh+mid+1);
    	else return std::min(calc(mh,rs),mis[k]);
    }
    inline void up(int l,int r,int k)
    {
    	mxv[k]=std::max(mxv[lc],mxv[rc]);
    	mis[k]=calc(mxv[rc],ls);
    }
    void build(int l,int r,int k)
    {
    	if(l==r)return mxv[k]=a[l],void();
    	build(ls),build(rs),up(l,r,k);
    }
    void upd(int l,int r,int k,int p)
    {
    	if(l==r)return mxv[k]=a[l],void();
    	p<=mid?upd(ls,p):upd(rs,p);up(l,r,k);
    }
    int main()
    {
    	int n=in(),m=in(),op=in(),ans=0;
    	for(int i=1;i<=n;++i)
    		a[i]=a[i+n]=in(),a[i]-=i,a[n+i]-=n+i;
    	build(1,n<<1,1);printf("%d
    ",ans=mis[1]+n-1);
    	for(int i=1;i<=m;++i)
    	{
    		int x=in()^op*ans,y=in()^op*ans;
    		a[x]=y-x;a[x+n]=y-x-n;
    		upd(1,n<<1,1,x),upd(1,n<<1,1,x+n);
    		printf("%d
    ",ans=mis[1]+n-1);
    	}
    	return 0;
    }
    
    

    毒瘤

    之前的博客是我没理解清写的。

    树的(dp)是基础,然后把返祖边们抠出来建虚树,枚举两端情况。

    这里的(f[u][0/1])是表示(u)(0/1)的时候,子树的方案数。

    所以我们只要枚举返祖边的祖先点的状态就可以了。

    然后处理转移系数(xs[u][i=0/1][j=0/1])表示虚树上的父亲选(i),这个点选(j)的系数的转移系数。

    注意到边是有影响的,所以不在虚树上的点的(xs)表示的是该点选(i),这个点子树内第一个虚点选(j)的系数,当我们发现这个(xs)转移到一个虚点时,直接把(xs)挂在后面那维代表的虚点上。

    不在虚树上的点记得乘上这个点选(0/1)的方案数。

    在虚树上的点的(xs)要使得(xs[u][0][0]=xs[u][1][1]=1)

    转移的时候如果这个点被强制选了某个值,另一的(dp)初值必须为(0)

    (code)

    #include<vector>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define pb push_back
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    typedef std::pair<int,int> P;
    #define mk std::make_pair
    #define fr first
    #define sc second
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    const int YL=998244353,N=1e5+5;typedef std::vector<int> vi;
    inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
    inline int MO(const int &x){return x>=YL?x-YL:x;}
    vi G[N],E[N];int xs[N][2][2],f[N][2],g[N][2],tsz[N],imp[N],o[N];
    int Eu[N],Ev[N],tot,dep[N],fg[N][2],vis[N],tt;
    void pre_dfs(int u,int pa=0)
    {
    	o[u]=++tt;
    	for(int v:G[u])if(v==pa)continue;
    		else if(!o[v])pre_dfs(v,u),tsz[u]+=tsz[v];
    		else
    		{
    			imp[u]=1;
    			if(o[u]<o[v])
    				Eu[++tot]=u,Ev[tot]=v;
    		}
    	imp[u]|=tsz[u]>=2;tsz[u]=tsz[u]||imp[u];
    }
    void mul(int f[2][2],int g[2][2])
    {
    	int f00=f[0][0],f01=f[0][1];
    	int f10=f[1][0],f11=f[1][1];
    	g[0][0]=MO(f00+f10);
    	g[0][1]=MO(f01+f11);
    	g[1][0]=f00,g[1][1]=f01;
    }
    inline void init(int f[2][2]){f[0][0]=f[1][1]=1,f[0][1]=f[1][0]=0;}
    int dfs(int u)
    {
    	vis[u]=g[u][0]=g[u][1]=1;int pos=0;
    	for(int v:G[u])
    		if(!vis[v])
    		{
    			int w=dfs(v);
    			if(!w)
    			{
    				g[u][0]=1ll*g[u][0]*(g[v][1]+g[v][0])%YL;
    				g[u][1]=1ll*g[u][1]*g[v][0]%YL;
    			}
    			else if(!imp[u])mul(xs[v],xs[u]),pos=w;
    			else mul(xs[v],xs[w]),E[u].pb(w),pos=w;
    		}
    	if(imp[u])return init(xs[u]),u;
    	xs[u][0][0]=1ll*xs[u][0][0]*g[u][0]%YL;
    	xs[u][0][1]=1ll*xs[u][0][1]*g[u][0]%YL;
    	xs[u][1][0]=1ll*xs[u][1][0]*g[u][1]%YL;
    	xs[u][1][1]=1ll*xs[u][1][1]*g[u][1]%YL;
    	return pos;
    }
    void dp(int u)
    {
    	f[u][0]=fg[u][1]?0:g[u][0];
    	f[u][1]=fg[u][0]?0:g[u][1];
    	for(int v:E[u])
    	{
    		dp(v);
    		for(int i=0;i<2;++i)
    			f[u][i]=(1ll*xs[v][i][0]*f[v][0]+1ll*xs[v][i][1]*f[v][1])%YL*f[u][i]%YL;
    	}
    }
    int main()
    {
    	int n=in(),m=in(),ans=0;
    	for(int i=1,u,v;i<=m;++i)
    		u=in(),v=in(),G[u].pb(v),G[v].pb(u);
    	pre_dfs(1),imp[1]=1,dfs(1);int mx=1<<tot;
    	for(int i=0;i<mx;++i)
    	{
    		for(int j=0;j<tot;++j)
    			if(i>>j&1)fg[Eu[j+1]][1]=1,fg[Ev[j+1]][0]=1;
    			else fg[Eu[j+1]][0]=1;
    		dp(1);ans=MO(ans+MO(f[1][1]+f[1][0]));		
    		for(int j=0;j<tot;++j)
    			if(i>>j&1)fg[Eu[j+1]][1]=0,fg[Ev[j+1]][0]=0;
    			else fg[Eu[j+1]][0]=0;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
    

    游戏

    我们发现如果一个点能到(L),且另一个点能到它,那么另一个点肯定能到(L),所以我们预处理(L[i],R[i])(i)点能扩大的最大范围。我们需要安排一个顺序使得他们最优。

    如果一扇门(x,x+1)的钥匙在(1-x)则肯定要先转移(x+1)再转移(x),反之亦然。

    于是我们可以拓扑排序。

    yyb的做法是假的

    问题的关键在于门是不满的,所以有很多没有关系的点之间跳来跳去,复杂度就假了。

    但是由于数据只卡了正着做的,没卡反着做的,于是他的乱搞就能过(他写了两篇乱搞(小声。

    那我们用并查集把没有门的点缩起来就(ok)了。

    (code)

    #include<queue>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    typedef std::pair<int,int> P;
    #define mk std::make_pair
    #define fr first
    #define sc second
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    const int N=1e6+5;
    int head[N],to[N],du[N],p[N],L[N],R[N],nxt[N],cnt,key[N],tot,n,fa[N];
    inline void add(int u,int v){to[++cnt]=v,nxt[cnt]=head[u],head[u]=cnt,++du[v];}
    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    inline void work(int u)
    {
    	int l=L[u],r=R[u],nl,nr;
    	while(1)
    	{
    		nl=l,nr=r;
    		while(l>1&&(!key[l-1]||(l<=key[l-1]&&key[l-1]<=r)))l=L[find(l-1)];
    		while(r<n&&(!key[ r ]||(l<=key[ r ]&&key[ r ]<=r)))r=R[find(r+1)];
    		if(nl==l&&nr==r)break;
    	}
    	L[u]=l,R[u]=r;
    }
    int main()
    {
    	n=in();int m=in(),q=in();
    	for(int i=1,x,y;i<=m;++i)x=in(),y=in(),key[x]=y;
    	for(int i=1;i<=n;++i)fa[i]=i;
    	for(int i=1;i<n;++i)if(!key[i])fa[i+1]=find(i);
    	for(int i=1;i<n;++i)
    		if(key[i])
    		{
    			if(key[i]<=i)add(find(i+1),find(i));
    			else add(find(i),find(i+1));
    		}
    	std::queue<int>Q;
    	for(int i=1;i<=n;++i)if(fa[i]==i&&!du[i])Q.push(i);
    	while(!Q.empty())
    	{
    		int u=p[++tot]=Q.front();Q.pop();
    		for(int i=head[u];i;i=nxt[i])
    			if(!--du[to[i]])Q.push(to[i]);
    	}
    	for(int i=1;i<=n;++i)R[find(i)]=i;
    	for(int i=n;i>=1;--i)L[find(i)]=i;
    	for(int i=1;i<=tot;++i)work(p[i]);
    	while(q--){int x=find(in()),y=find(in());puts(L[x]<=y&&y<=R[x]?"YES":"NO");}
    	return 0;
    }
    
    

    排列

    先把依赖关系的(DAG)建出来。

    然后就是贪心,小的一定要尽量放在前面。

    考虑当前最小值,它在父亲节点删掉后一定会被删。

    所以可以并起来,然后我们现在是考虑一堆序列的顺序,推下式子发现只要平均值小就一定先选,就没了。

    (code)

    #include<cstdlib>
    #include<vector>
    #include<queue>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    #define mk std::make_pair
    #define fr first
    #define sc second
    #define double long double
    typedef std::pair<double,int> P;
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    typedef std::vector<int> vi;
    const int N=5e5+5;vi G[N];
    const double eps=1e-6;
    int o[N],sz[N],fa[N],ff[N];ll w[N];
    struct Queue
    {
    	std::priority_queue<P>Q1,Q2;
    	void push(P x){Q1.push(x);}
    	void erase(P x){Q2.push(x);}
    	void upd(){while(!Q2.empty()&&Q1.top()==Q2.top())Q1.pop(),Q2.pop();}
    	inline void pop(){upd();Q1.pop();}
    	inline P top(){upd();return Q1.top();}
    }Q;
    int dfs(int u)
    {
    	int ans=u!=0;o[u]=1;
    	for(int v:G[u])
    		if(o[v])puts("-1"),exit(0);
    		else ans+=dfs(v);return ans;
    }
    int find(int x){return x==ff[x]?x:ff[x]=find(ff[x]);}
    int main()
    {
    	int n=in();ll ans=0;
    	for(int i=1;i<=n;++i)G[fa[i]=in()].push_back(i);
    	for(int i=1;i<=n;++i)ans+=w[i]=in();
    	if(dfs(0)!=n)return puts("-1"),0;
    	for(int i=1;i<=n;++i)ff[i]=i,sz[i]=1;
    	for(int i=1;i<=n;++i)Q.push(mk(-(double)w[i],i));
    	for(int i=1;i<=n;++i)
    	{
    		P now=Q.top();Q.pop();int u=find(now.sc),v=find(fa[u]);
    		if(v)Q.erase(mk(-(double)w[v]/sz[v],v));
    		ans+=w[u]*sz[v],w[v]+=w[u],sz[v]+=sz[u],ff[u]=v;
    		if(v)Q.push(mk(-(double)w[v]/sz[v],v));
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    

    道路

    普及(dp),设(F[i][j][k])表示第(i)个城市,没修的公路有(j)条,没修的铁路有(k)条的最小代价。

    叶子节点直接算,非叶子节点枚举修什么。考场上好像卡空间,用分治的(fft)的卡空间技巧就行了

    (code)

    #include<vector>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define gt getchar()
    #define ll long long
    #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    typedef std::pair<int,int> P;
    #define mk std::make_pair
    #define fr first
    #define sc second
    inline int in()
    {
    	int k=0;char ch=gt;bool p=1;
    	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
    	while(ch>'-')k=k*10+ch-'0',ch=gt;
    	return p?k:-k;
    }
    typedef std::vector<int> vi;
    const int N=20005;vi G[N];
    ll f[N][41][41],a[N],b[N],c[N];
    ll dfs(int u,int L,int R)
    {
    	if(u<0)return c[-u]*(a[-u]+L)*(b[-u]+R);
    	if(~f[u][L][R])return f[u][L][R];int lc=G[u][0],rc=G[u][1];
    	return f[u][L][R]=std::min(dfs(lc,L+1,R)+dfs(rc,L,R),dfs(lc,L,R)+dfs(rc,L,R+1));
    }
    int main()
    {
    	int n=in();
    	for(int i=1;i<n;++i)
    	{
    		int s=in(),t=in();
    		G[i].push_back(s),G[i].push_back(t);
    	}
    	for(int i=1;i<=n;++i)a[i]=in(),b[i]=in(),c[i]=in();
    	memset(f,-1,sizeof f);printf("%lld
    ",dfs(1,0,0));
    	return 0;
    }
    
    
  • 相关阅读:
    .net core相关博客
    通过几个Hello World感受.NET Core全新的开发体验
    ASP.NET Core 介绍
    ASP.NET MVC4企业级实战目录
    ASP.NET MVC4入门到精通系列目录汇总
    HTTP详解
    壮哉大微软,.Net人的春天来了,你准备好了嘛!
    简析 .NET Core 构成体系
    .Net Core[译文]
    OI再见
  • 原文地址:https://www.cnblogs.com/cx233666/p/10651955.html
Copyright © 2020-2023  润新知