• #4 CF568E & CF613E & CF587D


    Longest Increasing Subsequence

    题目描述

    点此看题

    解法

    首先有一个关键的 \(\tt observation\):由于本题求的是最长上升子序列,所以在求解最优解是每个数只出现一次这个限制是可以忽略的,因为最长上升子序列不可能包含重复的数。

    考虑魔改一下传统的 \(\tt LIS\) 做法:设 \(f_i\) 表示长度为 \(i\) 的最长上升子序列的结尾最小值,\(g_i\) 表示这个结尾的位置。那么非空位可以直接转移,空位可以双指针转移,暴力枚举所有填入的数即可。

    再考虑如何构造出最后的答案,对于非空位我们可以记录 \(l_i\) 表示以 \(i\) 结尾的最长上升子序列长度,\(p_i\) 表示这个最优序列的上一个位置。所以对于非空位我们可以直接跳到上一个位置,对于空位可以直接枚举上一个位置,复杂度没问题,总时间复杂度 \(O((n+m)k)\)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <map>
    using namespace std;
    const int M = 100005;
    const int inf = 0x3f3f3f3f;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,t,a[M],b[M],c[M],lst[M];
    int f[M],g[M],l[M],p[M];map<int,int> mp;
    int get(int x)
    {
    	return b[lower_bound(b+1,b+1+m,x)-b-1];
    }
    signed main()
    {
    	n=read();
    	for(int i=1,z=0;i<=n;i++)
    	{
    		a[i]=read();f[i]=inf;
    		lst[i]=z;if(a[i]==-1) z=i;
    	}
    	m=read();
    	for(int i=1;i<=m;i++) mp[b[i]=read()]++;
    	sort(b+1,b+1+m);
    	for(int i=1;i<=n;i++)
    	{
    		if(a[i]!=-1)
    		{
    			int j=lower_bound(f+1,f+1+t,a[i])-f;
    			p[i]=g[j-1];l[i]=j;
    			g[j]=i;f[j]=a[i];
    			if(f[t+1]<inf) t++;
    			continue;
    		}
    		int j=t+1;
    		for(int k=m;k>=1;k--)
    		{
    			while(j>0 && f[j-1]>=b[k]) j--;
    			f[j]=b[k];g[j]=i;
    		}
    		if(f[t+1]<inf) t++;
    	}
    	int nl=t,nv=f[t],i=g[t],j=1;
    	while(nl--)
    	{
    		if(a[i]!=-1)
    		{
    			if(a[p[i]]==-1) nv=get(a[i]);
    			i=p[i];continue;
    		}
    		c[i]=nv;mp[nv]--;int j=0;
    		for(int k=i-1;k>=1;k--)
    			if(l[k]==nl && a[k]<nv && a[k]!=-1)
    				{j=k;break;}
    		if(j) i=j;
    		else i=lst[i],nv=get(nv);
    	}
    	for(int i=1,j=1;i<=n;i++)
    	{
    		if(a[i]!=-1) c[i]=a[i];
    		else if(!c[i])
    		{
    			while(!mp[b[j]]) j++;
    			c[i]=b[j];mp[b[j]]--;
    		}
    	}
    	for(int i=1;i<=n;i++)
    		printf("%d ",c[i]);
    	puts("");
    }
    

    Puzzle Lover

    题目描述

    点此看题

    解法

    一定要注意是 \(2\times n\) 的矩阵,不是 \(n\times m\) 的矩阵哦,这启示我们可以去使用讨论法。

    我们先 \(m=1/2\) 的情况特判掉,然后考虑答案的形式一定是这样(嫖个 大佬 的图):

    那么所有路径都可以分成这三个部分,我用自然语言描述一下:

    • 部分一:从起点往左走,然后走到某个点绕回来,构成上下两个等长的段。
    • 部分二:如果新到这一列,那么可以走到同列的另一行,否则只能走到下一列。
    • 部分三:从部分二出来之后走到某个点绕回来,构成上下两个等长的段。

    发现部分一和部分三都是可以通过哈希轻易解决的,部分二需要决策貌似有点难搞。

    那么我们考虑只有部分二怎么处理,设 \(f[i][j][k]\) 表示现在在 \((i,j)\),匹配到目标串的第 \(k\) 位的方案数,并且一下步只能走下一列;设 \(g[i][j][k]\) 表示现在在 \((i,j)\),匹配到了字符串的第 \(k\) 位的方案数,下一步可以换行或者走下一列,那么这两个数组交替转移即可(实际上就是把能不能换列记录下来了):

    \[g[i][j+1][k+1]\leftarrow (f[i][j][k]+g[i][j][k]) \]

    \[f[2-i][j][k+1]\leftarrow g[i][j][k] \]

    然后再把部分一和部分三考虑进去,发现现在就可以整体 \(dp\) 了,我们枚举部分一的两个等长段,匹配上目标串的一个前缀,作为部分二 \(dp\) 的初始化;\(dp\) 之后再枚举部分三的两个等长段,匹配上目标串的一个后缀,来用 \(dp\) 值统计答案。

    统计答案还有一些细节:做完一次之后可以把目标串翻转然后再做一次,对于不需要部分二的方案我们算了两次,需要除掉。时间复杂度 \(O(n^2)\)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 2005;
    const int MOD = 1e9+7;
    #define ull unsigned long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans,gs,g[2][M][M],f[2][M][M];
    char s[2][M],t[M];ull h1[2][M],h2[2][M],h[M],pw[M];
    void add(int &x,int y) {x=(x+y)%MOD;}
    ull get1(int l,int r,int id) {return h1[id][r]-h1[id][l-1]*pw[r-l+1];}
    ull get2(int l,int r,int id) {return h2[id][l]-h2[id][r+1]*pw[r-l+1];}
    void dp()
    {
    	memset(f,0,sizeof f);
    	memset(g,0,sizeof g);
    	for(int i=1;i<=m;i++)
    		h[i]=h[i-1]*371+t[i];
    	for(int i=0;i<2;i++)
    		for(int j=1;j<=n;j++)
    			for(int k=j;k>=1;k--)
    			{
    				int len=2*(j-k+1);
    				if(len>m) break;
    				ull ha=get1(k,j,i),hb=get2(k,j,i^1);
    				if(ha+pw[j-k+1]*hb==h[len])
    				{
    					if(len==m) gs++;
    					else g[i][j][len]=1;
    				}
    			}
    	for(int i=0;i<2;i++)
    		for(int j=1;j<=n;j++)
    			if(s[i][j]==t[1]) g[i][j][1]=1;
    	for(int i=m;i>=1;i--)
    		h[i]=h[i+1]*371+t[i];//reverse the hash
    	for(int k=1;k<m;k++)
    		for(int j=1;j<=n;j++)
    			for(int i=0;i<2;i++)
    			{
    				if(j<n && s[i][j+1]==t[k+1])
    					add(f[i][j+1][k+1],g[i][j][k]),
    					add(f[i][j+1][k+1],f[i][j][k]);
    				if(s[i^1][j]==t[k+1])
    					add(g[i^1][j][k+1],f[i][j][k]);
    			}
    	for(int i=0;i<2;i++)
    	for(int j=1;j<=n;j++)
    	{
    		if(s[i][j]==t[m])
    		{
    			add(ans,f[i][j-1][m-1]);
    			add(ans,g[i][j-1][m-1]);
    		}
    		for(int k=j;k<=n;k++)
    		{
    			int len=(k-j+1)*2;
    			if(len>m) break;
    			ull ha=get1(j,k,i^1),hb=get2(j,k,i);
    			if(ha*pw[k-j+1]+hb==h[m-len+1])
    			{
    				if(len==m) gs++;
    				else add(ans,f[i][j-1][m-len]),
    				add(ans,g[i][j-1][m-len]);
    			}
    		}
    	}
    }
    signed main()
    {
    	scanf("%s%s%s",s[0]+1,s[1]+1,t+1);
    	n=strlen(s[0]+1);m=strlen(t+1);pw[0]=1;
    	if(m==1)
    	{
    		for(int i=0;i<2;i++)
    			for(int j=1;j<=n;j++)
    				ans+=s[i][j]==t[1];
    		printf("%d\n",ans);
    		return 0;
    	}
    	if(m==2)
    	{
    		for(int i=0;i<2;i++)
    			for(int j=1;j<=n;j++) if(s[i][j]==t[1])
    				ans+=(s[i^1][j]==t[2])+(s[i][j-1]==t[2])
    				+(s[i][j+1]==t[2]);
    		printf("%d\n",ans);
    		return 0;
    	}
    	for(int i=1;i<M;i++) pw[i]=pw[i-1]*371;
    	for(int i=0;i<2;i++)
    	{
    		for(int j=1;j<=n;j++)
    			h1[i][j]=h1[i][j-1]*371+s[i][j];
    		for(int j=n;j>=1;j--)
    			h2[i][j]=h2[i][j+1]*371+s[i][j];
    	}
    	dp();reverse(t+1,t+m+1);dp();
    	add(ans,gs/2);printf("%d\n",ans);
    }
    

    Duff in Mafia

    题目描述

    点此看题

    解法

    我真心觉得本题的一些性质可以大讨论,但是太难写了所以我放弃了,还是走算法的正途吧

    思考匹配带来的限制,一言以蔽之:匹配的两个边之间不能有共同端点,那么我们考虑两边带来的限制。这可以转化成一个 \(\tt 2-sat\) 问题,我们用下面的方法建图:

    • \(x_i\) 表示边 \(i\) 最终删除,\(x_i'\) 表示边 \(i\) 最终不删除,二分答案 \(c\)
    • 如果边权 \(>c\),代表这条边不能删除,\(x_i\) 连向 \(x_i'\)
    • 对于两个共点的边 \(i,j\),不能同时被删除,\(x_i\) 连向 \(x_j'\)
    • 对于两个共点同色的边 \(i,j\),在最后不能共存,\(x_i'\) 连向 \(x_j\)

    考虑共点同色的边数 \(\leq 2\),所以第三类边可以直接暴力连。但是第二类边需要优化建图,一个很显然的思考是用前后缀拼出这个 \(i\not=j\),那么我们就建两层点分别表示前后缀的虚点,稍微想一下怎么连边即可。

    为了卡常我们可以只在二分的时候建第一类边,时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <stack>
    using namespace std;
    #define pb push_back
    const int M = 1000005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    //choose : x , not choose : x+m
    int n,m,k,cnt,a[M],b[M],c[M],d[M],id[M];
    int num,Ind,low[M],dfn[M],col[M],in[M];
    vector<int> g[M],v[M];stack<int> s;
    void add(int x,int y) {g[x].pb(y);};
    void tarjan(int u)
    {
    	low[u]=dfn[u]=++Ind;
    	s.push(u);in[u]=1;
    	for(auto v:g[u])
    	{
    		if(!dfn[v])
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(in[v])
    			low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		int v=0;num++;
    		do {
    			v=s.top();s.pop();
    			col[v]=num;in[v]=0;
    		}while(v!=u);
    	}
    }
    int check(int x)
    {
    	for(int i=1;i<=m;i++)
    		if(d[i]>x) add(i,i+m);
    	for(int i=1;i<=cnt;i++) dfn[i]=0;
    	num=Ind=0;
    	for(int i=1;i<=cnt;i++)
    		if(!dfn[i]) tarjan(i);
    	for(int i=1;i<=m;i++)
    		if(d[i]>x) g[i].pop_back();
    	for(int i=1;i<=m;i++)
    		if(col[i]==col[i+m]) return 0;
    	return 1;
    }
    signed main()
    {
    	n=read();m=read();cnt=m<<1;
    	for(int i=1;i<=m;i++)
    	{
    		a[i]=read();b[i]=read();
    		c[i]=read();d[i]=read();
    		v[a[i]].pb(i);v[b[i]].pb(i);
    	}
    	for(int i=1;i<=n;i++)
    	{
    		sort(v[i].begin(),v[i].end(),
    		[](int &x,int &y){return c[x]<c[y];});
    		for(int j=0,k=0,l=v[i].size();j<l;j=k)
    		{
    			while(k<l && c[v[i][j]]==c[v[i][k]]) k++;
    			if(k-j>2) {puts("No");return 0;}
    			if(k-j==2)
    				add(v[i][j]+m,v[i][j+1]),
    				add(v[i][j+1]+m,v[i][j]);
    		}
    		int lst=0;
    		for(auto x:v[i])
    		{
    			int y=++cnt;
    			if(lst) add(y,lst),add(x,lst);
    			add(y,x+m);lst=y;
    		}
    		reverse(v[i].begin(),v[i].end());lst=0;
    		for(auto x:v[i])
    		{
    			int y=++cnt;
    			if(lst) add(y,lst),add(x,lst);
    			add(y,x+m);lst=y;
    		}
    	}
    	int l=0,r=1e9,ans=-1;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;
    		if(check(mid)) ans=mid,r=mid-1;
    		else l=mid+1;
    	}
    	if(ans==-1) {puts("No");return 0;}
    	check(ans);
    	for(int i=1;i<=m;i++)
    		if(col[i]<col[i+m]) id[++k]=i;
    	puts("Yes");printf("%d %d\n",ans,k);
    	for(int i=1;i<=k;i++) printf("%d ",id[i]);
    }
    
  • 相关阅读:
    CEIWEI CommTone串口调试精灵7.1 串口调试 串口工具
    CEIWEI USBMonitor USB监控精灵 v2.3.2 USB过滤驱动 USB监控
    CommMonitor8.0 串口过滤驱动 SDK DLL版本 C#/Delphi调用DEMO
    CommMonitor10.0.3串口过滤工具(serial port monitor)
    (1)、JEasyUI 之 Datagrid的Combobox 显示 textField 值的问题
    button 使用 flex 布局的兼容性问题
    探索 Reflect.apply 与 Function.prototype.apply 的区别
    awk 输出前 N 列的最简单方法
    在 Ubuntu 18.04 下安装 fcitx 及搜狗拼音输入法
    禁用 Gnome Shell 默认的 Ubuntu Dock 和 Ubuntu AppIndicators 扩展
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15876771.html
Copyright © 2020-2023  润新知