集训2

爆零次数喜加一。果然考前打卡不是好文明。

T1思路Fake,T2看都没看直接输出0,T3调了一个小时只拿了20分,换了数据之后开心地看着T3爆零(😊,T4一眼区间DP,调了很久很久过了样例(但只是过了样例),剩下十分钟想打暴力就来不及了。分数出来之后甚至还暗自高兴了一小下(嘿嘿嘿至少没爆零),只不过下午一来就变成0了(😭

对此,我只想说:

寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄 寄

T1 交通

n个点2n条有向边,显然会构成不少环,我一直以为只需要判断重边+ans*2就能A之,后来才发现自己变成了小丑。

把某个点的出边之间或者入边之间连边(没错就是把边看成结点,这样一定能构成若干个偶环,一条边删除(某个点入边或出边),那么另一个边(某个点的另一条入边或出边)就一定要保留,同时连带着这条边所连着的另一个点,这样,一个环就只有两种方案,方案数即为2的环数次方取mod。

#include <bits/stdc++.h>
#define Reg register 
using namespace std;
const int maxn=200010,mod=998244353;
int n,cnt;
int ru[maxn][2];
int chu[maxn][2];
int fa[maxn];
bool vis[maxn];
int ans;
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar(); 
	}
	return s*w;
}
inline int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
inline void merge(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx!=fy) fa[fx]=fy;
}
inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=(res%mod*a%mod)%mod;
		a=(a%mod*a%mod)%mod;
		b>>=1; 
	}
	return res;
}
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	n=read();
	for(Reg int i=1;i<=2*n;++i) fa[i]=i;
	for(Reg int i=1;i<=2*n;++i){
		int x,y;x=read();y=read();cnt++;
		if(!chu[x][0]) chu[x][0]=cnt;
		else chu[x][1]=cnt;
		if(!ru[y][0]) ru[y][0]=cnt;
		else ru[y][1]=cnt;
	}
	for(int i=1;i<=cnt;++i){
		merge(chu[i][0],chu[i][1]);
		merge(ru[i][0],ru[i][1]);
	}
	for(int i=1;i<=cnt;++i){
		if(!vis[find(i)]) vis[find(i)]=1,ans++;
	}
	printf("%d",qpow(2,ans)%mod);
	return 0;
} 

T2 冒泡排序

不会。

kaguya巨佬的代码:

#include <bits/stdc++.h>
#define Reg register
#define int long long 
using namespace std;
const int maxn=5010,mod=1e9+7;
int n,fl,ans;
int tr1[maxn],tr2[maxn];
int dre1[maxn],dre2[maxn];
int a[maxn],f[maxn][maxn]; 
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar(); 
	}
	return s*w;
}
inline int lowbit(int x){return x&(-x);}
inline void update(int pos,int val,int *tr)
{
	while(pos<=n) tr[pos]+=val,pos+=lowbit(pos);
}
inline int dopre(int pos,int key)
{
	if(key==1) return tr1[pos]+dre1[pos-lowbit(pos)];
	else   return tr2[pos]+dre2[pos-lowbit(pos)];
}
signed main(){
	freopen("mp.in","r",stdin);
	freopen("mp.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i)
	{
		 a[i]=read();
		 //注意0~n-1的序列,所以应该++
		 if((++a[i])==i) fl=1;
		 //这样的情况无论如何也交换不了 
		 else if(a[i]>i)
		 	//关系一:a[i]要往后换 
		 	update(i,1,tr1),update(a[i]-1,-1,tr1);
		 else
		 	//关系二:a[i]要往前换 
		 	update(a[i],1,tr2),update(i-1,-1,tr2);
	} 
	if(fl) return printf("0"),0;
	for(int i=1;i<n;++i)
	{
		dre1[i]=dopre(i,1);dre2[i]=dopre(i,2);
		if(dre1[i]&&dre2[i]) return printf("0"),0;
	} 
	dre1[0]=dre2[0]=dre1[n]=dre2[n]=0;
	f[1][1]=1;//开始DP(泪,到后面就懵逼了
	for(int i=2,j=1;j<n;j=i,i++)
	{
		if(dre1[j])
		{
		for(int k=2;k<=i;k++) f[i][k]=(f[i][k-1]+f[j][k-1])%mod;
		}
		else if(dre2[j])
		{
		for(int k=j;k;k--) f[i][k]=(f[i][k+1]+f[j][k])%mod;
		}
		else
		{
			for(int k=1;k<=j;k++) f[i][1]+=f[j][k],f[i][1]%=mod;
			for(int k=2;k<=i;k++) f[i][k]=f[i][k-1];
		}
	}	
	for(int i=1;i<n;++i) ans=(ans+f[n-1][i])%mod;
	printf("%d",ans);
	return 0;
} 

T3 矩阵

如果是个\(3 \times 3\)的矩阵,那么很明显有性质:\(a-c-b+g+e-h\)为定值(反正我没看出来),以此推广到一个\(n \times m\)的矩阵,那么我们就知道(我不知道)只要前两行前两列能削成\(0\),整个矩阵就一定能变为\(0\)。可以先把前两行前两列都化为对角线相同的形式,一个对角线一个对角线地削。如果这样削完了矩阵还不能变成\(0\),那么它这辈子都别想变成\(0\)了。delov大佬的做法是先削对角线再削行列,本质上跟之前那个做法是相同的,只不过好像更容易一些(?

考场上甚至连大样例都没想过去模,joke巨佬模了大样例拿了50pts。

#include <bits/stdc++.h>
#define Reg register 
#define ll long long
using namespace std;
const int maxn=1410,mod=998244353,inf=2147483642;
int n,m,cnt,l,r,Maxx;
ll BE[maxn][maxn],tmp;
inline ll read(){
	ll s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar(); 
	}
	return s*w;
}
struct ces{
	ll opt,x,k;
}ans[maxn*10];
int main(){
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	n=read();m=read();
	for(Reg int i=1;i<=n;++i)
		for(Reg int j=1;j<=m;++j)
			BE[i][j]=read();
	//tmp=BE[2][2]-BE[1][1];
	//for(int j=1;j<=m;++j) BE[1][j]+=tmp;
//	ans[++cnt].opt=1,ans[cnt].x=1,ans[cnt].k=tmp;
	for(int i=2;i<=n;++i){
		tmp=BE[i-1][1]-BE[i][2];
		ans[++cnt].opt=1,ans[cnt].x=i,ans[cnt].k=tmp;
		for(int j=1;j<=m;++j) BE[i][j]+=tmp;
	}
	for(int i=2;i<=m;++i){
		tmp=BE[1][i-1]-BE[2][i];
		ans[++cnt].opt=2,ans[cnt].x=i,ans[cnt].k=tmp;
		for(int j=1;j<=n;++j) BE[j][i]+=tmp;
	}
	for(int i=1-n;i<=m-1;i++){
		if(i<=0) tmp=-BE[1-i][1];
		if(i>0) tmp=-BE[1][1+i];
		ans[++cnt].opt=3,ans[cnt].x=i,ans[cnt].k=tmp;
		for(int j=1;j<=m;++j){
			if(j-i>=1&&j-i<=n) BE[j-i][j]+=tmp;
		}
	}
	for(Reg int i=1;i<=n;++i){
		for(Reg int j=1;j<=m;++j){
			if(BE[i][j]!=0)	return printf("-1"),0;
		}
	}
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;++i) printf("%lld %lld %lld\n",ans[i].opt,ans[i].x,ans[i].k);
	return 0;
} 
/*
3 4
4 5 1 2
7 2 1 4
0 3 1 9
*/

T4 花瓶

麻了,我一个多小时都按着区间DP打的,没想到居然是线性(😭),在最后时刻卡过了样例,只不过到那时候已经算是纯摆烂了,样例就算过了看调试信息也能看出来根本不对。

\(f[i][j]\)来表示已经走到了第i个状态,前一个分隔的位置是j的最大美丽度,\(s[i]\)维护到i位置时美丽度的前缀和,转移方程即:\(f[i][j]=max(f[j][k])+(s[i]-s[j])*(s[j]-s[k]),(k \ge 0 ,k < j)\)。边界条件为:\(f[i][0]=0\)(不分区间时为\(0\)),其他都初始化为\(-inf\),暴力三层循环能拿到50pts,可是想A之还需要另想他法。

"——简单的斜率优化”

啊对对对,通过把式子化简能得出 \(f[j][k]=(s[i]-s[j])*s[k]+p \)(p为常数),于是我们就可以对其维护一个斜率单调递减的上凸包,及时排除无用决策,用\((s[i]-s[j])\)不断地往凸包上靠,一直到斜率大于它时,此点就可以被取出当作最优解。

还有三个必须要注意的问题是:

  • 由于\(s[i]\)并非单调递增(有负数存在),我们往凸包里面插点时不好维护它的单调性,那就记录每个前缀和的位置并将其排序,当且仅当符合位置条件时\((k<j,i>j)\)往其中插入或取出决策,保证单调。

  • 如果按\(f[i][j]\)中的\(i\)为状态,\(f[j][k]\)就变成了无法调节的量,所以要把j作为状态,放在外层循环。

  • \(i\)正序或者倒序都可以,如果倒序的话应该往队头斜率最大的那一块取出决策,如果正序的话可以从队尾斜率最小的部分取决策。

然后就没有了。

看的某一位学长的代码:

#include <bits/stdc++.h>
#define Reg register 
#define int long long
using namespace std;
const int maxn=5010,inf=999999999;
int n,ans,l,r,s[maxn];
int xus[maxn],id[maxn];
int a[maxn],sum[maxn]; 
int f[maxn][maxn];
int q[maxn*2];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar(); 
	}
	return s*w;
}
inline int slope(int fa,int fb,int a,int b){
	return (fa-fb)*(a-b);
}
inline bool cmp(int a,int b){ 
	return sum[a]<sum[b];
}
signed main(){
	freopen("d.in","r",stdin);
	freopen("d.out","w",stdout);
	n=read();
	memset(f,-0x3f,sizeof(f)); f[0][0]=0;
	for(int i=1;i<=n;++i) f[i][0]=0,a[i]=read(),sum[i]=sum[i-1]+a[i],xus[i]=i;
	//xus数组排序用
	sort(xus,xus+1+n,cmp); 
	for(int j=1;j<n;++j){
		int l=1,r=0;
		//f[j][k]是变化量而非常量,故要把j放在最外层当做状态 
		for(int k=0;k<=n;k++){
			if(xus[k]>=j) continue;
			while(l<r&&slope(f[j][q[r]],f[j][q[r-1]],sum[xus[k]],sum[q[r-1]])<=slope(f[j][xus[k]],f[j][q[r-1]],sum[q[r]],sum[q[r-1]])) r--;
			q[++r]=xus[k];
		}
		for(int i=n;i>=0;i--){
			if(xus[i]<=j) continue;
			while(l<r&&(sum[xus[i]]-sum[j])*(sum[q[l+1]]-sum[q[l]])<=(f[j][q[l+1]]-f[j][q[l]])) l++;
			f[xus[i]][j]=max(f[xus[i]][j],f[j][q[l]]+(sum[xus[i]]-sum[j])*(sum[j]-sum[q[l]]));
		}
	} 
	for(int i=0;i<n;++i) ans=max(ans,f[n][i]);
	printf("%lld",ans);
	return 0;
} 

还有我爱subtask,它给了我这个蒟蒻又一次爆零的好机会。

posted @ 2022-06-06 21:39  Broken_Eclipse  阅读(18)  评论(0)    收藏  举报

Loading