Test 2022.10.12

今天是紫黑专场

T1 \(GreedyChange\)

分析

说实话我并没有太搞懂这道黑题,要我解释的话也并不能太清楚地说出来,只是对着题解老老实实整理了一遍,迷迷糊糊地打出来,

大概就是对于贪心的算法想办法去构造一组\(hack\)数据,想办法对于当前的面值\(M\),让\(dp\)可以从大小适中的数中选几个出来达成目标,然而贪心选了一个很大的(几乎接近\(M\))数之后,必须花更多的代价去选数量更多的零碎面值,然后达成\(hack\)的效果

注意还需要用到一个学术论文里提到的一个结论来过

Code

#include<bits/stdc++.h>
#define re register
using namespace std;
int n,ans=-1;
int a[1000];
signed main() 
{
	scanf("%d",&n);
    for(re int i=1;i<=n;++i)scanf("%d",&a[i]);
    for(re int i=1;i<=n;++i)
    {
    	for(re int j=i+1;j<=n;++j) 
		{
            int ans1=0,ans2=1,S=a[i]-1,s;
            for(re int k=i+1;k<=j;++k)
			{
                ans2=ans2+S/a[k];
                S%=a[k];
            }
            s=S=a[i]+a[j]-1-S;
            for(re int k=1;k<=n;++k)
			{
                ans1=ans1+S/a[k];
                S%=a[k];
            }
            if((ans==-1||s<ans)&&ans2<ans1)ans=s;
        }
	}
	printf("%d",ans);

    return 0;
}

T2 qmh的美食盛宴

又是一道人类智慧题了,说起来比较麻烦,但是这是我第一道写完的非裸三分题,还是得好好写一写纪念一下

分析

很容易想到对于给出的两个矩阵,我们作差,得到一个差量矩阵,根据题意就可以在这一个矩阵上面对每一行或者每一列进行\(+-1\)的操作了,先分析样例吧:

\[\begin{bmatrix} 1\quad 1\quad 1\\ 1\quad 1\quad 1\\ 1\quad 1\quad 1\\\\ 3\quad 2\quad 2\\ 2\quad 1\quad 1\\ 2\quad 1\quad 1\\ \end{bmatrix} \]

对于这一组样例,我们写出来的差量矩阵是这样的(上减下或者下减上都可):

\[\begin{bmatrix} 2\quad 1\quad 1\\ 1\quad 0\quad 0\\ 1\quad 0\quad 0\\ \end{bmatrix} \]

我们把每一次的操作记录在数组里面,对应列的操作记为\(x[j]\),行的操作记为\(y[i]\)。那么很容易写出样例的一组解

\[\begin{bmatrix} x[]={-2,-1,-1}\\ y[]\\ =\\ 0\\ 1\\ 1 \end{bmatrix} \]

为了方便理解,我们再写一个差量矩阵出来:

\[\begin{bmatrix} 1\quad 2\quad 3\\ 4\quad 5\quad 6\\ 7\quad 8\quad 9\\ \end{bmatrix} \]

同样的可以写出来我们的操作数组

\[\begin{bmatrix} x[]={-1,-2,-3}\\ y[]\\ =\\ 0\\ -3\\ -6 \end{bmatrix} \]

\(Point^1\)

显而易见的是,我们当前构造出来的答案就是:

\[\sum_{j=1}^{m}|x[j]|+\sum_{i=1}^{n}|y[i]| \]

那么如果通过当前的这组解不能构造出来一组可行解了,就证明一定无解吗?

\(Point^2\)

显而易见的是,我们写出来的操作数组不一定就是当前的最优解,那么就像\(exgcd\)一样,我们考虑怎么通过当前的一组解构造出另外一组解。

很容易想到的是,我们对所有的\(x[j]+1\),那么所有的\(y[i]\)必须\(-1\),这时候我们的答案就变成了

\[\sum_{j=1}^{m}|x[j]+1|+\sum_{i=1}^{n}|y[i]-1| \]

这里的答案和变化前可能一样,也可能不一样,具体怎么样要涉及到初中的数学知识了,你可以这样把答案展开(这里把\(-1\)泛化成\(k\)了)

\[f(k)=|x[1]+k|+|x[2]+k|+......+|x[m]+k|+|y[1]-k|+|y[2]-k|+......+|y[n]-k| \]

对于所能够被构造出来的答案,我们一定都能这样变化它,很容易被证明的是,无论是我们构造出来的答案,还是最后的最优解,一定都是上式的一个特例,所以如果按照我们的构造方法没有答案,那就一定是无解了

很明显的就是一个零点讨论了,这里记操作变化值为\(k\)的时候答案为\(f(k)\)

你可以很清楚地知道在实数域(\(k\in R\)是非常显而易见的)上面,\(f(k)\)一定不是一个单调的函数,所以不能使用二分了,那么还有什么方法解决这道题呢?

\(Solution1\):模拟退火

很明显的是,对于任意一个函数,退火都有找出正确答案的可能性,我们不知道这个函数是单峰还是多峰,但我们完全可以使用退火来求解,只是可能调参会有一些麻烦

\(Solution2\):三分

言下之意就是我们需要证明这是一个单峰函数了

我们把以\(k\)为自变量画出来的零点分别投影到数轴上面:\(-x[1],-x[2]...-x[m]......y[1],y[2]...y[n]\)

image

假设\(A,B,C,D,E,F,G,H\)是我们的零点,而\(I,K,J\)是我们的自变量所在的点,我们把零点按照坐标绝对值大小从小到大依次两两分组成若干个区间\([A,H],[B,G],[C,F],[D,E]\),根据绝对值的几何意义,当一个点处于一个区间之内,它到两个端点的距离是不变的,所以当\(K,J\)处于\([D,E]\)区间时,他们被所有区间包含,所以无论在这个区间内怎么移动,最后的答案都是不会变的。

现在我们开始把自变量所在的点往\([D,E]\)之外的区间移动,比如我们现在的\(I\)点(可能被遮住了,反正是蓝色的线)所在的位置,明显的看到,\(I\)\([A,H],[B,G],[C,F]\)的距离是没有发生变化的,但是到\([D,E]\)的距离变远了,所以一定不如刚刚被所有区间包括的情况优秀,同理可证,当这个点越远离最中间的区间,最后的答案就会越来越大,向左和向右移动都是如此(显而易见),所以最后画出来的图像应该是这样的:
image

注意到这里\([D,E]\)的答案是不变的,图中斜率也相对表示出来了

有人会问:如果零点个数是奇数个,是不是就不能这么做了?

实际上也是很简单的,我们可以把排序后,所有零点的中位数当成两个点,组成区间时把它当成一个\([MIDNUM,MIDNUM]\)的长度为\(0\)的区间,那么最后的答案就必须在这个点上面了

这种单峰函数就可以用三分来解决了。

\(Solution3\):直接排序

实际上我们或许用不到三分这种算法,如我们刚刚提到,把所有零点排序后的中间那个点取出(或者是中间那个区间),我们最优的答案

就是取出的点(或者是区间),言下之意,我们只需要用一个排序统计所有零点就行了,最后再算一下所有区间的长度和。

Code

#include<bits/stdc++.h>
#define re register 
#define int long long
using namespace std;
const int maxn=1e6+100;
inline int read()
{
	int x=0,f=1;
	char c=getchar();
	while(!isdigit(c))
	{
		if(c=='-')f=-1;
		c=getchar();
	}
	while(isdigit(c))
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
int n,m;int x[maxn],y[maxn];
inline int abs1(int x){return x>0?x:-x;}
int calc(int tmp)
{
	int ans=0;
	for(re int i=1;i<=m;++i)ans+=abs1(x[i]+tmp);
	for(re int i=1;i<=n;++i)ans+=abs1(y[i]-tmp);
	return ans;
}
int search(int l,int r)
{
	if(l>=r)return l;
	int lmid=l+(r-l)/3,rmid=r-(r-l)/3;
	if(calc(lmid)<calc(rmid))return search(l,rmid-1);
	else return search(lmid+1,r);
}
signed main()
{
//	freopen("eatshxt5.in","r",stdin);
	int T=read();
	while(T--)
	{
		n=read(),m=read();
		int a[n+2][m+2],b[n+2][m+2],v[n+2][m+2];
		for(re int i=1;i<=n;++i)for(re int j=1;j<=m;++j)a[i][j]=read();
		for(re int i=1;i<=n;++i)for(re int j=1;j<=m;++j)b[i][j]=read(),v[i][j]=a[i][j]-b[i][j];
		for(re int j=1;j<=m;++j)x[j]=0-v[1][j];
		int flag=1;
		for(re int i=1;i<=n;++i)
		{
			y[i]=0-(v[i][1]+x[1]);
			for(re int j=2;j<=m;++j)if(y[i]!=0-(v[i][j]+x[j])){flag=0;goto A;}
		}
		A:
		if(!flag){puts("-1");continue;}
		printf("%lld\n",calc(search(-1000000000,1000000000)));
	}

	return 0;
}

T3 恶魔之树

听说是一个特别恐怖的状压,我打了个基于质因数分解的\(20pts\)搜索就跑路了,当然还可以特判全部相同的点,这个时候答案为:

\[ans=1+a[1]\times \sum_{i=1}^nC_n^i=a[1]\times (2^n-1)+1 \]

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define re register 
#define int long long
using namespace std;
const int maxn=3e5+100;
const int MOD=998244353;
int fn[maxn][310];int prime[maxn],cnt;bool isp[maxn];int lim=-1;int n,a[maxn];
int maxt[310];int ans=1ll;
int power(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans;
}
void dfs(int p,int com,int pos/*上一个选的位置*/)
{
	if(com==p)
	{
		int an=1;
		for(re int i=1;i<=lim;++i)an=an*power(prime[i],maxt[i])%MOD;
		ans=(ans+an)%MOD;
		return ;
	}
	for(re int i=pos+1;i<=n;++i)
	{
		int tmp[310];
		for(re int j=1;j<=lim;++j)tmp[j]=maxt[j],maxt[j]=max(maxt[j],fn[i][j]);
		dfs(p,com+1,i);
		for(re int j=1;j<=lim;++j)maxt[j]=tmp[j];
	}
}
void get_factor(int number,int x)
{
	if(x==1)return ;
	for(int i=1;prime[i]<=x&&i<=x;i++)
	{
		if(x%prime[i]==0)
		{
			int t=x;lim=max(lim,i);
			while(!(t%prime[i]))
			{
				t/=prime[i];
				fn[number][i]++;
			}
		}
	}
}
void get_prime(int x)
{
	for(re int i=2;i<=x*x;++i)
	{
		if(prime[cnt]>x)return ;
		if(!isp[i])prime[++cnt]=i;
		for(re int j=1;j<=cnt&&i*prime[j]<=x;++j)
		{
			isp[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
inline int read()
{
	int x=0,f=1;
	char c=getchar();
	while(!isdigit(c))
	{
		if(c=='-')f=-1;
		c=getchar();
	}
	while(isdigit(c))
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
int flag=1;
signed main()
{
	n=read();
	get_prime(300);
	for(re int i=1;i<=n;++i)
	{
		a[i]=read(),get_factor(i,a[i]);
		if(i==1)continue;
		if(a[i]!=a[i-1])flag=0;
	}
	if(flag==1)
		ans=(ans+(power(2,n)-1+MOD)%MOD*a[1])%MOD;
	else
		for(re int i=1;i<=n;++i)dfs(i,0,0);
	printf("%lld",ans);
	return 0;
}

T4 大魔法师

题意

简单来说给出一个区间,每个区间内的元素有\(A,B,C\)三种值,给出七种操作:

\[opt1:A_i=A_i+B_i\\ opt2:B_i=B_i+C_i\\ opt3:C_i=C_i+A_i,i\in[l,r]\\ opt4:A_i=A_i+v\\ opt5:B_i=B_i\times v\\ opt6;C_i=v\\ opt7:对[l,r]内的A,B,C分别求和 \]

分析

说实话要我来写纯线段树的话我肯定写不出来,因为光加一个乘法,线段树的操作就多了一大堆,更别说这么多\(tag\)

我想不到,但有人能想到用线段树套矩阵

对于每一个元素,我们定义一个向量

\[\begin{bmatrix} A_i&B_i&C_i&1 \end{bmatrix} \]

写出对应的转移矩阵:

\[opt1: \begin{bmatrix} 1&0&0&0\\ 1&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}\\ opt2: \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&1&1&0\\ 0&0&0&1 \end{bmatrix}\\ opt3: \begin{bmatrix} 1&0&1&0\\ 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}\\ opt4: \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&1&0\\ V&0&0&1 \end{bmatrix}\\ opt5: \begin{bmatrix} 1&0&0&0\\ 0&V&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}\\ opt6: \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&0&0\\ 0&0&V&1 \end{bmatrix}\\ \]

剩下的就是线段树区间修改的板子了,注意这里矩阵乘法之所以正确,是因为矩阵乘法是有结合律的,就像我们的\(lazytag\)一样,对区间进行延时操作

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define rea register 
#define int long long
const int maxn=1e4;
const int MOD=998244353;
using namespace std;
int a[maxn],b[maxn],c[maxn];
int n,m,opt,l,r,v;
template <typename T>inline void re(T &x) {
	x=0;
	int f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=-f;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
	x*=f;
	return;
}
template <typename T>void wr(T x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) wr(x/10);
	putchar(x%10^'0');
	return;
}
signed main()
{
	re(n);
	for(rea int i=1;i<=n;++i)re(a[i]),re(b[i]),re(c[i]);
	re(m);
	for(rea int cnt=1;cnt<=m;++cnt)
	{
		re(opt),re(l),re(r);
		if(opt==1)
			for(rea int i=l;i<=r;++i)a[i]=(a[i]+b[i])%MOD;
		else if(opt==2)
			for(rea int i=l;i<=r;++i)b[i]=(b[i]+c[i])%MOD;	
		else if(opt==3)
			for(rea int i=l;i<=r;++i)c[i]=(c[i]+a[i])%MOD;
		else if(opt==4)
		{
			re(v);
			for(rea int i=l;i<=r;++i)a[i]=(a[i]+v)%MOD;
		}
		else if(opt==5)
		{
			re(v);
			for(rea int i=l;i<=r;++i)b[i]=(b[i]*v)%MOD;
		}
		else if(opt==6)
		{
			re(v);
			for(rea int i=l;i<=r;++i)c[i]=v;
		}
		else if(opt==7)
		{
			int ansa=0,ansb=0,ansc=0;
			for(rea int i=l;i<=r;++i)ansa=(ansa+a[i])%MOD,ansb=(ansb+b[i])%MOD,ansc=(ansc+c[i])%MOD;
			printf("%lld %lld %lld\n",ansa,ansb,ansc);	
		}
	}
	return 0;
}
posted @ 2022-10-14 15:23  Hanggoash  阅读(4)  评论(0编辑  收藏  举报
动态线条
动态线条end