NOIP模拟61

T1 交通

解题思路

把环视为点,对于原图中每一个点的两条入边以及两条出边分别连边。

优于保证了原图中每个点出入度都是 2 因此新图中一定由若干个偶数环所组成的。

并且对于环中一定是只能间隔着选点,因此每个环有 2 种选择,答案就是 \(2^{环数}\)

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
int n,ans,in[N][2],out[N][2],t1[N],t2[N],fa[N<<1];
int find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
int power(int x,int y)
{
	int temp=1;
	while(y)
	{
		if(y&1) temp=temp*x%mod;
		x=x*x%mod; y>>=1;
	}
	return temp;
}
signed main()
{
	freopen("a.in","r",stdin); freopen("a.out","w",stdout);
	n=read();
	for(int i=1,x,y;i<=2*n;i++)
	{
		fa[i]=i; x=read(); y=read();
		in[y][t1[y]++]=i; out[x][t2[x]++]=i;
	}
	for(int i=1;i<=n;i++)
	{
		if(find(in[i][0])!=find(in[i][1])) fa[find(in[i][0])]=find(in[i][1]);
		if(find(out[i][0])!=find(out[i][1])) fa[find(out[i][0])]=find(out[i][1]);
	}
	for(int i=1;i<=2*n;i++) ans+=(find(i)==i);
	printf("%lld",power(2,ans)%mod);
	return 0;
}

T2 小P的单调数列

解题思路

\(n^3\) 的 DP 比较好想, \(f_{i,j}\) 表示默认选择第 \(i\) 个并且当前段数为 \(j\) 个的情况直接暴力枚举当前个,前一个,以及对应段数就好了。

可以发现只选择一个最长上升子序列和一个最长下降子序列其实是最优的方案,因为如果再加入一段序列答案会变优的情况只能是新加入序列比上升段或者下降段的总和更大的时候。。

这种情况显然是不存在的,时间复杂度降到 \(n^2\) 在加上棵线段树就可以达到 \(nlogn\) 的复杂度了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,INF=1e18;
int n,cnt,lsh[N],s[N];
double ans;
struct Segment_Tree
{
	int tre[N<<2];
	void push_up(int x){tre[x]=max(tre[ls],tre[rs]);}
	void insert(int x,int l,int r,int pos,int val)
	{
		if(l==r) return tre[x]=max(tre[x],val),void();
		int mid=(l+r)>>1;
		if(pos<=mid) insert(ls,l,mid,pos,val);
		else insert(rs,mid+1,r,pos,val);
		push_up(x);
	}
	int query(int x,int l,int r,int L,int R)
	{
		if(L>R) return -INF;
		if(L<=l&&r<=R) return tre[x];
		int mid=(l+r)>>1,maxn=-INF;
		if(L<=mid) maxn=max(maxn,query(ls,l,mid,L,R));
		if(R>mid) maxn=max(maxn,query(rs,mid+1,r,L,R));
		return maxn;
	}
}T[3];
signed main()
{
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	n=read(); for(int i=1;i<=n;i++) lsh[i]=s[i]=read();
	sort(lsh+1,lsh+n+1); cnt=unique(lsh+1,lsh+n+1)-lsh-1;
	for(int i=1;i<=n;i++) s[i]=lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh;
	for(int i=1;i<=n;i++)
		for(int k=2;k>=1;k--)
		{
			if(k==1) T[1].insert(1,1,cnt,s[i],lsh[s[i]]),ans=max(ans,(1.0*lsh[s[i]])/(1.0*k));
			int temp=T[k-1].query(1,1,cnt,s[i],s[i]);
			if(k&1) temp=max(temp,max(T[k].query(1,1,cnt,1,s[i]-1),T[k-1].query(1,1,cnt,s[i]+1,cnt)));
			else temp=max(temp,max(T[k-1].query(1,1,cnt,1,s[i]-1),T[k].query(1,1,cnt,s[i]+1,cnt)));
			T[k].insert(1,1,cnt,s[i],temp+lsh[s[i]]);
			ans=max(ans,1.0*(1.0*temp+1.0*lsh[s[i]])/(1.0*k));
		}
	printf("%.3lf",ans);
	return 0;
}

T3 矩阵

解题思路

其实我们只要考虑如何消掉前两行和前两列就好了(证明就。。)

假设矩阵数组是 \(s\) 那么先把 \(s_{1,1}\)\(s_{2,2}\) 搞成相同的,接下来对于前两行,我们肯定是先把他们每一列都消成一样的最后再统一干掉。

那么我们已经确定了 \(s_{2,2}\) 就可以对应确定 \(s_{1,2}\) 相应的 \(s_{2,3}\) 也被确定了,以此类推,最后一个可以直接更改。

对于两列的情况也是相同的方法

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e3+10;
int n,m,cnt,s[N][N];
struct Node{int opt,x,dat;}q[N*6];
void check(){cout<<"Check:  "<<cnt<<endl;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)cout<<s[i][j]<<' ';cout<<endl;}}
signed main()
{
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			s[i][j]=read();
	if(n==1){cout<<m<<endl;for(int i=1;i<=m;i++)printf("2 %lld %lld\n",i,-s[1][i]);exit(0);}
	if(m==1){cout<<n<<endl;for(int i=1;i<=n;i++)printf("1 %lld %lld\n",i,-s[i][1]);exit(0);}
	if(s[1][1]!=s[2][2])
	{
		int temp=s[2][2]-s[1][1];
		q[++cnt]=(Node){1,1,temp};
		for(int i=1;i<=m;i++) s[1][i]+=temp; 
	}
	for(int i=2;i<m;i++)
		if(s[1][i]!=s[2][i])
		{
			int temp=s[2][i]-s[1][i];
			q[++cnt]=(Node){3,i-1,temp};
			for(int x=1,y=i;x<=n&&y<=m;x++,y++)
				s[x][y]+=temp;
		}
	if(s[1][m]!=s[2][m])
	{
		q[++cnt]=(Node){3,m-1,s[2][m]-s[1][m]};
		s[1][m]=s[2][m];
	}
	for(int i=2;i<n;i++)
		if(s[i][1]!=s[i][2])
		{
			int temp=s[i][2]-s[i][1];
			q[++cnt]=(Node){3,1-i,temp};
			for(int x=i,y=1;x<=n&&y<=m;x++,y++)
				s[x][y]+=temp;
		}
	if(s[n][1]!=s[n][2])
	{
		q[++cnt]=(Node){3,1-n,s[n][2]-s[n][1]};
		s[n][1]=s[n][2];
	}
	for(int i=1;i<=m;i++)
		if(s[1][i])
		{
			q[++cnt]=(Node){2,i,-s[1][i]};
			for(int j=n;j>=1;j--)
				s[j][i]-=s[1][i];
		}
	for(int i=1;i<=n;i++)
		if(s[i][1])
		{
			q[++cnt]=(Node){1,i,-s[i][1]};
			for(int j=m;j>=1;j--)
				s[i][j]-=s[i][1];
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(s[i][j])
				printf("-1"),exit(0);
	printf("%lld\n",cnt);
	for(int i=1;i<=cnt;i++) printf("%lld %lld %lld\n",q[i].opt,q[i].x,q[i].dat);
	return 0;
}

T4 花瓶

解题思路

斜率优化 DP

\(f_{i,j}\) 表示当前到 \(i\) 之前的端的右端点是 \(j\) 的最优解, \(s_i\) 表示前缀和

\[f_{i,j}=\max\limits_{0\le k<j}\{f_{j,k}+(s_i-s_j)(s_j-s_k)\} \]

\[f_{i,j}=(s_i-s_j)s_j+\max\limits_{0\le k<j}\{f_{j,k}-(s_i-s_j)s_k\} \]

对于 \(s_a<s_b\) 并且 \(b\) 优于 \(a\) 就有:

\[\dfrac{f_{j,a}-f_{j,b}}{s_a-s_b}>(s_i-s_j) \]

因此我们可以枚举原序列的 \(j\) 并且对于按照 \(s\) 排完序后按照 \(s\) 从大到小进行枚举 \(i\)

并且对于每一次的标号小于 \(j\) 的维护出一个上凸包,枚举 \(i\) 的时候更新答案就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e3+10;
int INF,n,ans,q[N],a[N],f[N][N],pre[N];
struct Node
{
	int id,dat;
	bool friend operator < (Node x,Node y)
	{
		if(x.dat==y.dat) return x.id<y.id;
		return x.dat<y.dat;
	}
}s[N];
signed main()
{
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read(); memset(f,0x8f,sizeof(f)); INF=f[0][0];
	for(int i=1;i<=n;i++) a[i]=read(),pre[i]=s[i].dat=s[i-1].dat+a[i],f[i][0]=f[i][i]=0,s[i].id=i;
	sort(s+0,s+n+1);
	for(int j=1;j<=n;j++)
	{
		int head=1,tail=0;
		for(int i=0;i<=n;i++)
		{
			if(s[i].id>=j) continue;
			while(head<tail&&(f[j][s[i].id]-f[j][s[q[tail]].id])*(s[q[tail]].dat-s[q[tail-1]].dat) >= (f[j][s[q[tail]].id]-f[j][s[q[tail-1]].id])*(s[i].dat-s[q[tail]].dat) )tail--;
			q[++tail]=i;
		}
		for(int i=n;i>=0;i--)
		{
			if(s[i].id<=j) continue;
			while(head<tail&&(f[j][s[q[head]].id]-f[j][s[q[head+1]].id])<(s[i].dat-pre[j])*(s[q[head]].dat-s[q[head+1]].dat)) head++;
			f[s[i].id][j]=max(f[s[i].id][j],(s[i].dat-pre[j])*pre[j]+f[j][s[q[head]].id]-(s[i].dat-pre[j])*s[q[head]].dat);
		}
	}
	for(int i=0;i<=n;i++) ans=max(ans,f[n][i]); printf("%lld",ans);
	return 0;
}
posted @ 2021-09-27 06:36  Varuxn  阅读(42)  评论(0编辑  收藏  举报