题目链接
点击打开链接
题目解法
牛牛牛
显然转化成差分数组做,令 \(b_i=a_{i+1}-a_i\)
考虑不同操作给 \(b\) 带来的影响
- \(l=1,r=n\),没影响
- \(l=1,r<n\),\(b_r:=b_r+x\)
- \(l>1,r=n\),\(b_{l-1}:=b_{l-1}+x\)
- \(l>1,r<n\),\(b_{l-1}:=b_{l-1}+x,\;b_r:=b_r-x\)
所以操作相当于单点加和两个数和相同,随便改
我们把操作看成连边,则有一个结论:
在最优策略中,对于一个连通块,令其 \(b\) 之和为 \(S\),则有:
- \(S=0\),连通块为一棵树
- \(S\neq 0\),连通块为一棵树 + 一个自环
应该可以构造性证明,我只口胡了一下,差不多就是:
- 对于树,从叶子往上操作,目标是使叶子为 \(0\),父亲跟着动就好了,最后整棵树都为 \(0\)
- 对于一个自环和树,把树根定于自环,然后做树的操作,最后对着自环做一下就好了
我们用状压 \(dp\) 做这东西,记一下当前状态的最小操作数和方案数
然后每次添加一个连通块转移即可(树的话就是 \(prufer\) 序列的 \(siz^{siz-2}\),加一个自环就 \(\times 2siz\),因为有 \([1,t],[t+1,n]\) 两种方法改变 \(t\) 处的值
时间复杂度 \(O(3^{n-1})\)
#include <bits/stdc++.h>
#define F(i,x,y) for(int i=(x);i<=(y);i++)
#define DF(i,x,y) for(int i=(x);i>=(y);i--)
#define ms(x,y) memset(x,y,sizeof(x))
#define SZ(x) (int)x.size()-1
#define all(x) x.begin(),x.end()
#define pb push_back
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T> void chkmax(T &x,T y){ x=max(x,y);}
template<typename T> void chkmin(T &x,T y){ x=min(x,y);}
template<typename T> void read(T &FF){FF=0;int RR=1;char ch=getchar();for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;FF*=RR;
}
const int N=18,P=1e9+7;
int n,a[N],b[N],ppc[1<<N],wys[N],f[1<<N],g[1<<N];
LL sum[1<<N];
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
#define lowbit(x) x&-x
int main(){read(n);F(i,0,n-1) read(a[i]);F(i,0,n-2) b[i]=a[i+1]-a[i];int full=(1<<(n-1))-1;F(S,1,full){ppc[S]=__builtin_popcount(S);F(i,0,n-2) if(S>>i&1){ sum[S]=sum[S^1<<i]+b[i];break;}}F(i,1,n-1){wys[i]=1;F(j,1,i-2) wys[i]=1ll*wys[i]*i%P;}ms(f,0x3f);f[0]=0,g[0]=1;F(S,1,full){int nS=S^lowbit(S);for(int nT=nS;;nT=(nT-1)&nS){int T=nT^lowbit(S);int nf=f[S^T]+ppc[T]-1+(sum[T]!=0),ng=1ll*g[S^T]*wys[ppc[T]]%P;if(sum[T]) ng=2ll*ng*ppc[T]%P;if(nf<f[S]) f[S]=nf,g[S]=0;if(nf==f[S]) inc(g[S],ng);if(!nT) break;}}F(i,1,f[full]) g[full]=1ll*g[full]*i%P;printf("%d\n%d\n",f[full],g[full]);return 0;
}