按照强度排序后,相邻的三个一组。
#include
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=300005;
int n,a[N];LL ans;
int main()
{
n=read();
for(RI i=1;i<=n*3;++i) a[i]=read();
sort(a+1,a+1+n*3);
for(RI i=n*3-1;i>n;i-=2) ans+=(LL)a[i];
printf("%lld\n",ans);
return 0;
}
读入所有询问后反向处理。染色的时候,在每个节点上记录它是被离它多远的点染色的,这样可以剪枝。
#include
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005;
int n,m,tot,js,Q;
int h[N],ne[N<<1],to[N<<1],wk[N],X[N],D[N],C[N],col[N],q[N],dis[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void work(int x,int d,int c) {
if(d<=wk[x]) return;
wk[x]=d;
if(!col[x]) col[x]=c;
if(!d) return;
for(RI i=h[x];i;i=ne[i]) work(to[i],d-1,c);
}
int main()
{
int x,y;
n=read(),m=read();
for(RI i=1;i<=m;++i) x=read(),y=read(),add(x,y),add(y,x);
Q=read();
for(RI i=1;i<=Q;++i) X[i]=read(),D[i]=read(),C[i]=read();
for(RI i=1;i<=n;++i) wk[i]=-1;
for(RI i=Q;i>=1;--i) work(X[i],D[i],C[i]);
for(RI i=1;i<=n;++i) printf("%d\n",col[i]);
return 0;
}
假设已经构造出来的字符串,前半部分是X后半部分是Y,对于一个新字符c,将原字符串改成cXcY贡献增加一倍,改成cXYc增加一,则倍增即可。
#include
using namespace std;
#define RI register int
typedef long long LL;
LL bin[42],n;
int a[205],js,len,k;
int main()
{
bin[0]=1;for(RI i=1;i<=40;++i) bin[i]=bin[i-1]<<1LL;
scanf("%lld",&n);++n;
for(RI i=40;i>=0;--i) if(n&bin[i]) {k=i;break;}
for(RI i=k-1;i>=0;--i) {
++js;
for(RI j=len;j>=len/2+1;--j) a[j+2]=a[j];
a[len/2+2]=js;
for(RI j=len/2;j>=1;--j) a[j+1]=a[j];
a[1]=js,len+=2;
if(n&bin[i]) {
++js;
for(RI j=len;j>=1;--j) a[j+1]=a[j];
a[1]=a[len+2]=js,len+=2;
}
}
printf("%d\n",len);
for(RI i=1;i<=len;++i) printf("%d ",a[i]);
puts("");
return 0;
}
发现最后一定是一些颜色中最轻的一部分球可以任意变动位置,这些球构成了一个“集合”,其他球位置保持不变。
所以检验一下每种颜色最轻的球可不可以与所有球中最轻的球(最轻的球的颜色则不用这个检验)交换位置,如果可以,哪些球又能和这种颜色的最轻球交换位置,最后得到每种颜色这个在这个集合里有多少个球,按照有重复元素的排列公式可以算出结果。
#include
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int mod=1e9+7,N=200005,inf=0x3f3f3f3f;
vector<int> orz[N];
int mi=inf,mic,mii,X,Y,ans,js,n,a[N],fac[N],ni[N];
int ksm(int x,int y) {
int re=1;
for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
return re;
}
int main()
{
int c,w;
n=read(),X=read(),Y=read();
for(RI i=1;i<=n;++i) {
c=read(),w=read(),orz[c].push_back(w);
if(w<=mi) mii=mi,mi=w,mic=c;
else if(w<mii) mii=w;
}
for(RI i=1;i<=n;++i) sort(orz[i].begin(),orz[i].end());
for(RI i=1;i<=n;++i) {
if(!(int)(orz[i].size())) continue;
if(i==mic) {
a[i]=1,++js;
for(RI j=1;j<orz[i].size();++j)
if(orz[i][j]+mii<=Y||orz[i][j]+orz[i][0]<=X) ++a[i],++js;
}
else {
if(orz[i][0]+mi>Y) continue;
a[i]=1,++js;
for(RI j=1;j<orz[i].size();++j)
if(orz[i][j]+mi<=Y||orz[i][j]+orz[i][0]<=X) ++a[i],++js;
}
}
fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
ni[n]=ksm(fac[n],mod-2);
for(RI i=n-1;i>=0;--i) ni[i]=1LL*ni[i+1]*(i+1)%mod;
ans=fac[js];
for(RI i=1;i<=n;++i) ans=1LL*ans*ni[a[i]]%mod;
printf("%d\n",ans);
return 0;
}
显然,最多进行 l o g log log次跳跃。假设确定了当前驼峰的储水量,会发现可以把绿洲划分为一条条的线段,每条线段内部的绿洲可以通过行走互相到达,否则就需要跳跃。若把储水量为 V V V时的线段看做第0层,为 V / 2 V/2 V/2时看做第1层,为 V / 4 V/4 V/4时看做第2层……可以发现遍历绿洲就是从每层取一条线段覆盖所有绿洲。
对于第0层的一条线段,如果默认第0层选它,能不能遍历绿洲呢?
接下来就是状压DP了。首先预处理存好每层的线段,以及每层覆盖每个绿洲的线段的左右端点。设 f [ z t ] f[zt] f[zt], z t zt zt是一个二进制状态,表示有哪些层被取了线段, f f f记录取了这些线段后覆盖 [ 1 , x ] [1,x] [1,x]这个区间,能覆盖到的最大 x x x。这个转移就通过枚举当前状态下下一次取哪一层的线段,然后取那一层包含 f [ z t ] + 1 f[zt]+1 f[zt]+1这个绿洲的线段即可。
也同样DP出一个 g [ z t ] g[zt] g[zt],覆盖 [ x , n ] [x,n] [x,n]能覆盖到的最小 x x x。
有了f和g,我们就可以知道 m i [ i ] mi[i] mi[i]:当 [ 1 , i ] [1,i] [1,i]被覆盖时, [ x , n ] [x,n] [x,n]能被覆盖到的最小 n n n。
有了mi后,我们就可以判断第0层强制选某条线段后,其他绿洲能不能被覆盖了。
#include
using namespace std;
#define RI register int
int read() {
int q=0,w=1;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q*w;
}
const int N=200005,LIM=524290,inf=0x3f3f3f3f;
int n,V,m;
int bin[21],x[N],L[N],f[LIM],g[LIM],in[21][N],ans[N],mi[N];
vector<int> seg[21];
void prework() {
seg[m].push_back(0);
for(RI i=2;i<=n;++i)
if(x[i]-x[i-1]>V) seg[m].push_back(i-1);
seg[m].push_back(n);
int now=1;
for(RI i=1;i<seg[m].size();++i)
while(now<=seg[m][i]) in[m][now]=i,++now;
}
void work1() {
for(RI i=0;i<bin[m]-1;++i) {
for(RI j=0;j<m;++j)
if(!(i&bin[j]))
f[i|bin[j]]=max(f[i|bin[j]],seg[j+1][in[j+1][f[i]+1]]);
}
}
void work2() {
for(RI i=0;i<bin[m];++i) g[i]=n+1;
for(RI i=0;i<bin[m]-1;++i) {
for(RI j=0;j<m;++j)
if(!(i&bin[j]))
g[i|bin[j]]=min(g[i|bin[j]],seg[j+1][in[j+1][g[i]-1]-1]+1);
}
}
void work3() {
for(RI i=0;i<=n;++i) mi[i]=inf;
for(RI i=0;i<bin[m];++i)
mi[f[i]]=min(mi[f[i]],g[(bin[m]-1)^i]);
for(RI i=n-1;i>=0;--i) mi[i]=min(mi[i],mi[i+1]);
}
int main()
{
n=read(),V=read();
bin[0]=1;for(RI i=1;i<=20;++i) bin[i]=bin[i-1]<<1;
for(RI i=1;i<=n;++i) x[i]=read();
prework();while(V) V>>=1,++m,prework();
work1(),work2(),work3();
for(RI i=1;i<seg[0].size();++i) {
int L=seg[0][i-1]+1,R=seg[0][i];
if(mi[L-1]<=R+1) for(RI j=L;j<=R;++j) ans[j]=1;
}
for(RI i=1;i<=n;++i) puts(ans[i]?"Possible":"Impossible");
return 0;
}
当我已经确定好前 2 i − 1 2i-1 2i−1个 a a a后,若添加两个比中位数大的数,中位数变为被添加了的数中它的后继。若添加两个比中位数小的数,则变为前驱。若添加一大一小,则不变。
将 a a a从小到大排序,显然第 i i i个中位数的值在区间 [ a i , a 2 n − i ] [a_i,a_{2n-i}] [ai,a2n−i]中。
先假设 a a a是个排列。
设 f ( i , j , k ) f(i,j,k) f(i,j,k)表示已经确定了第 i i i个及之后的 b b b,且确定 b i b_i bi前其可能能选的数值中,有 j j j个小于等于 b i + 1 b_{i+1} bi+1, k k k个大于 b i + 1 b_{i+1} bi+1的方案数。
初始值 f ( n , 1 , 0 ) = 1 f(n,1,0)=1 f(n,1,0)=1
当状态 f ( i , j , k ) f(i,j,k) f(i,j,k)往 f ( i − 1 , ? , ? ) f(i-1,?,?) f(i−1,?,?)转移时, b i b_i bi的备选方案相比 b i + 1 b_{i+1} bi+1,增加了 a i a_i ai和 a 2 n − i a_{2n-i} a2n−i两种,所以 j j j和 k k k分别加1。
每此从 f ( i , ? , ? ) f(i,?,?) f(i,?,?)往 f ( i − 1 , ? , ? ) f(i-1,?,?) f(i−1,?,?)转移时,已经确定好的(然而没体现在DP状态里)的 a a a应该要被删除两个。
接下来,假设选择备选方案中, b i b_i bi往左数第 L L L个(因为 j j j的含义是小于等于,所以这个左数是从选 b i b_i bi开始算选第一个),那么由于 b i b_i bi应当是 b i + 1 b_{i+1} bi+1的前驱,所以往左数第 L − 1 L-1 L−1到第 1 1 1个被纳入备选方案的数,都应该在之前的移动中,删除一定的 a a a时,被删除掉了,所以转移到 f ( i , j + 1 − L + 1 , k + 1 + ( L > 1 ) ) f(i,j+1-L+1,k+1+(L>1)) f(i,j+1−L+1,k+1+(L>1))。
选 b i b_i bi往右数第 R R R个,同理,转移到 f ( i , j + 1 + 1 , k + 1 − R ) f(i,j+1+1,k+1-R) f(i,j+1+1,k+1−R)。
那么问题来了,是不是所有备选方案里的数都能选呢?
其实是可以的,虽然我不会严谨证明,不过可以用CY证明法证一下:
从前往后推,每次删a时:若b值往右移,要删两个较小数,都删较小数中最大的;若b值不变,要删一个较小一个较大,较小的那个删较小数中最大的。那么每次取新的b时,都能取到备选方案中最小的数。
若改一下,删一次较小数中最小的,就能取到备选方案中第二小的数。
……
现在将排列扩展到一般序列,只需要改一个地方:若 a i = a i + 1 a_i=a_{i+1} ai=ai+1,则每次移动后, j j j的值并没有+1( b b b的可选值种类没有增加)。同样,若 a 2 n − i = a 2 n − i − 1 a_{2n-i}=a_{2n-i-1} a2n−i=a2n−i−1, k k k的值也没有+1。
#include
using namespace std;
#define RI register int
const int mod=1e9+7;
int n,ans,f[102][102][102],a[102];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
scanf("%d",&n);
for(RI i=1;i<=n+n-1;++i) scanf("%d",&a[i]);
sort(a+1,a+n+n);
f[n][1][0]=1;
for(RI i=n-1;i>=1;--i) {
int pl=(a[i]!=a[i+1]),pr=(a[n+n-i]!=a[n+n-i-1]);
for(RI j=0;j<=n+n-1;++j)
for(RI k=0;j+k<=n+n-1;++k) {
if(!f[i+1][j][k]) continue;
for(RI L=1;L<=j+pl;++L)
f[i][j+pl-L+1][k+pr+(L>1)]=qm(f[i][j+pl-L+1][k+pr+(L>1)]+f[i+1][j][k]);
for(RI R=1;R<=k+pr;++R)
f[i][j+pl+1][k+pr-R]=qm(f[i][j+pl+1][k+pr-R]+f[i+1][j][k]);
}
}
for(RI i=0;i<=n+n-1;++i)
for(RI j=0;j<=n+n-1;++j) ans=qm(ans+f[1][i][j]);
printf("%d\n",ans);
return 0;
}