2019-5-23 雜題選講

2019-5-23 雜題選講

T1 [PKUSC2018]最大前綴和

給定一個長爲nn的整數序列,求全排列的最大前綴和(必須包含第一個數)之和。
1n20,1i=1na[i]1091\leq n \leq20,1\leq \sum_{i=1}^n \vert a[i] \vert \leq 10^9

正解

考慮序列的一個子集SS對答案的貢獻,也就是考慮一個子集SS的所有元素之和能成爲多少個排列的最大前綴和。

一個子集的排列TT能成爲最大和前綴的充要條件是:1,它的最大前綴和是自己;2,除 TT外後面那段的最大前綴和小於等於零

於是定義f[S]f[S]表示子集SS能組成最大前綴和是自己的排列數,定義g[S]g[S]表示子集SS能組成的最大前綴和小於等於零的排列數

至於ffgg的更新方式是由它們的性質決定,ff的性質在於“除本身外的每個後綴和都大於零”,gg的性質在於“每個前綴和都小於等於零”,於是就可以考慮gg的狀態由sum[S]<=0sum[S]<=0的轉移而來(並且加入一個元素後和任然非正),而ff的狀態則可以由sum[S]>0sum[S]>0的狀態加入一個元素來轉移(加入後的和的正負不關心)。

答案 Ssum[S]f[S]f[S]\sum_{S} sum[S]*f[S]*f[全集-S]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50;
const int M=5e6+5;
const int MOD=998244353;
int n,tot;
ll a[N],sum[M],f[M],g[M],Ans=0;
int lowbit(int x)
{
	return x&-x;
}
int main()
{
	scanf("%d",&n);
	tot=1<<n;
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[1<<(i-1)]=a[i];
	for (int i=1;i<tot;i++) sum[i]=sum[i^(lowbit(i))]+sum[lowbit(i)];
	g[0]=1;
	for (int i=1;i<=n;i++) f[1<<(i-1)]=1;
	for (int i=1;i<tot;i++)
	{
		if (sum[i]>=0)
		{
			for (int j=1;j<=n;j++)
				if (!((i>>(j-1))&1)) 
					f[i|(1<<(j-1))]=(f[i|(1<<(j-1))]+f[i])%MOD;
		}
		else
		{
			for (int j=1;j<=n;j++)
				if ((i>>(j-1))&1) 
					g[i]=(g[i]+g[i^(1<<(j-1))])%MOD;
		}
	}
	for (int i=0;i<tot;i++) Ans=(Ans+(sum[i]+MOD)%MOD*f[i]%MOD*g[(tot-1)^i]%MOD)%MOD;
	printf("%lld\n",Ans);
	return 0;
}

T2 CF1163D Mysterious Code

有三個字符串 c,s,tc,s,t,其中cc只包含小寫字母和星號,s,ts,t只包含小寫字母。
現在你可以把cc中每一個星號替換成一個小寫 字母(不同的星號可以替換成不同的字母),使得sscc中出現次數減去ttcc中出現次數最大。求出這個最大值。
1c1000,1s,t501 \leq\vert c \vert \leq 1000,1 \leq\vert s \vert,\vert t \vert\leq 50

正解

定義dp[i][j][k]dp[i][j][k]cc串匹配到第ii位時sstt分別已經匹配了jjkk位時的最大答案。
如果c[i]c[i]*就枚舉字符全集取最大的一個做答案轉移,否則直接轉移,其中並不是所有狀態都能轉移到,用visvis記錄,並且第一次拓展到某個狀態時不能直接取maxmax,因爲答案可以小於零。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
char c[N],s[N],t[N];
int l1,l2,l3,nxt1[N],nxt2[N],p,dp[N][60][60],vis[N][60][60];
int Ans=-1e9;
int main()
{
//	freopen("testdata.in","r",stdin);
    scanf("%s",c+1),l1=strlen(c+1);
    scanf("%s",s+1),l2=strlen(s+1);
    scanf("%s",t+1),l3=strlen(t+1);
    p=0;
    for (int i=2;i<=l2;i++)
    {
        while (p&&s[p+1]!=s[i]) p=nxt1[p];
        if (s[p+1]==s[i]) p++;
        nxt1[i]=p;
    }
    p=0;
    for (int i=2;i<=l3;i++)
    {
        while (p&&t[p+1]!=t[i]) p=nxt2[p];
        if (t[p+1]==t[i]) p++;
        nxt2[i]=p;
    }
    dp[0][0][0]=0;
    vis[0][0][0]=1;
    for (int i=1;i<=l1;i++)
        for (int j=0;j<l2;j++)
            for (int k=0;k<l3;k++) 
            {
                int L='a',R='z';
                if (c[i]!='*') L=R=c[i];
                for (int cc=L;cc<=R;cc++)
                {
                    int tmp=0;
                    char ch=cc;
                    if (!vis[i-1][j][k]) continue;
                    int p1=j,p2=k;
                    while (p1&&s[p1+1]!=ch) p1=nxt1[p1];
                    if (s[p1+1]==ch) p1++;
                    while (p2&&t[p2+1]!=ch) p2=nxt2[p2];
                    if (t[p2+1]==ch) p2++;
                    if (p1==l2) p1=nxt1[p1],tmp++;
                    if (p2==l3) p2=nxt2[p2],tmp--;
                    if (!vis[i][p1][p2]) dp[i][p1][p2]=dp[i-1][j][k]+tmp;
                    else dp[i][p1][p2]=max(dp[i][p1][p2],dp[i-1][j][k]+tmp);
                    vis[i][p1][p2]=1;
                    if (i==l1) Ans=max(Ans,dp[i][p1][p2]);
                }
            }
    printf("%d\n",Ans);
    return 0;
}

T3 CF1139E Maximize Mex

一個學校有nn個人mm個社團。一開始每人都恰好在一個社團裏。第ii人有能力值p[i]p[i]。接下來有dd天,在第ii天,第k[i]k[i]個人離開他所在的社團。
每天人離開之後,就會從每個社團中選出一個人(已經沒人的社團不管)。對於每一天,求出這些人的能力值mexmex的最大值。(mexmex是沒出現過的最小非負整數)
輸入的所有數是非負整數且5000\leq5000

正解

匈牙利求二分圖最大匹配,左部點是能力值,右部點是社團,由於能力值要求總零開始連續,符合匈牙利的思想,刪邊按套路倒過來加邊。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,p[N],c[N],d,k[N];
int tag[N];
int tot=0,f[N],nxt[N<<1];
int Ans[N],dxx=0,dfn[N],match[N];
struct Edge
{
	int u,v;
}e[N<<1];
int read()
{
	int x=0,f=1;
	char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return f*x;
}
void Add(int u,int v)
{
	tot++;
	nxt[tot]=f[u];
	f[u]=tot;
	e[tot]=(Edge){u,v};
	return;
}
int DFS(int x)
{
	for (int j=f[x];j!=-1;j=nxt[j])
	{
		int v=e[j].v;
		if (dfn[v]==dxx) continue;
		dfn[v]=dxx;
		if ((!match[v])||DFS(match[v]))
		{
			match[v]=x;
			return 1;
		}
	}
	return 0;
}
int main()
{
	memset(f,-1,sizeof(f));
	n=read(),m=read();
	for (int i=1;i<=n;i++) p[i]=read(),p[i]++;
	for (int i=1;i<=n;i++) c[i]=read();
	d=read();
	for (int i=1;i<=d;i++) k[i]=read(),tag[k[i]]=-1;
	for (int i=1;i<=n;i++) if (tag[i]!=-1) Add(p[i],10000+c[i]);
	for (int i=d;i>=1;i--)
	{
		dxx++;
		Ans[i]=Ans[i+1];
		for (int j=Ans[i]+1;j<=m;j++)
		{
			dxx++;
			if (DFS(j)) Ans[i]++;
			else break;
		}
		Add(p[k[i]],10000+c[k[i]]);
	}
	for (int i=1;i<=d;i++) printf("%d\n",Ans[i]);
	return 0;
}

T4[HNOI2015]亞瑟王

你有nn張牌,有rr個回合。每張牌有p[i]p[i]d[i]d[i]
對於每個回合,從編號爲11到編號爲nn的牌輪流考慮
對於每張牌:

  1. 若此牌已發動過,跳過;(如果本牌是最後一張牌,結束本回合)
  2. 若此牌沒發動,將有p[i]p[i]的概率發動它,並造成d[i]d[i]的傷害。(如果發動,本回合結束)
    求造成傷害的期望。(有T組數據)
    1T144,1n220,0r132,0&lt;p[i]&lt;1,0d[i]10001\leq T\leq 144,1\leq n\leq 220,0\leq r\leq 132,0&lt;p[i]&lt;1,0\leq d[i]\leq 1000

正解

由題面可知,每張卡片在整個rr輪遊戲中要麼發動一次要麼不發動。
假設第ii張牌被髮動的概率爲f[i]f[i]那麼答案就是i=1nf[i]d[i]\sum_{i=1}^n f[i]*d[i]

明顯地,這個f[i]f[i]不等於p[i]p[i],因爲每回合如果發動一張牌成功就會立馬結束該回合,這導致了決定f[i]f[i]的因素不僅有p[i]p[i],還有rr的大小以及卡牌的順序(越在後面發動概率消減越多)。

定義dp[i][j]dp[i][j]表示,在rr輪遊戲中,前i張卡牌當中有jj張被髮動的概率,定義有些奇怪但是能把決定f[i]f[i]的因素都囊括。
於是就有f[i]=j=0rdp[i1][j](1(1p[i])rj)f[i]=\sum_{j=0}^r dp[i-1][j]*(1-(1-p[i])^{r-j})
對於每次循環,由於我們知道了ii前面有jj張卡牌已發動,所以有rjr-j輪第i張卡牌是否發動與前i1i-1張無關,只要別每次都發動不了就行了

dpdp數組的求法:
dp[i][j]dp[i][j]dp[i][j1]dp[i][j-1]dp[i1][j1]dp[i-1][j-1]轉移而來,思想類似
dp[i][j]=dp[i1][j1](1(1p[i])rj+1)+dp[i1][j](1p[i])rjdp[i][j]=dp[i-1][j-1]*(1-(1-p[i])^{r-j+1})+dp[i-1][j]*(1-p[i])^{r-j}

#include<bits/stdc++.h>
using namespace std;
const int N=300;
int T,n,r;
double p[N],d[N],qpow[N][N],f[N],dp[N][N],Ans;
int main()
{
	scanf("%d",&T);
	while (T--)
	{
		Ans=0;
		memset(f,0,sizeof(f));
		memset(dp,0,sizeof(dp));
		scanf("%d%d",&n,&r);
		for (int i=1;i<=n;i++) scanf("%lf%lf",&p[i],&d[i]),qpow[i][0]=1;
		for (int i=1;i<=n;i++)
			for (int j=1;j<=r;j++) qpow[i][j]=qpow[i][j-1]*(1-p[i]);
		f[1]=1-qpow[1][r];
		dp[1][0]=qpow[1][r];
		dp[1][1]=1-qpow[1][r];
		for (int i=2;i<=n;i++)
			for (int j=0;j<=min(i,r);j++)
			{
				dp[i][j]+=dp[i-1][j]*qpow[i][r-j];
				if (j>0) dp[i][j]+=dp[i-1][j-1]*(1-qpow[i][r-j+1]);
			}
		for (int i=1;i<=n;i++)
			for (int j=0;j<=r;j++) f[i]+=dp[i-1][j]*(1-qpow[i][r-j]);
		for (int i=1;i<=n;i++) Ans+=f[i]*d[i];
		printf("%.9lf\n",Ans);
	}
	return 0;
}

T5 [THUPC2019]過河卒二

題意見鏈接

正解

1,當沒有障礙時,(1,1)&gt;(n,m)(1,1)-&gt;(n,m)出界的答案等於(1,1)&gt;(n+1,m+1)(1,1)-&gt;(n+1,m+1)右上角的答案,是這題的關鍵。因爲當出界後想走到(n+1,m+1)(n+1,m+1)只有一種走法,一一對應。該方案數的求法,則是枚舉斜着走有多少步,再乘上剩下正常走的方案數。

2,當有障礙時,容斥定理即可。設狀態壓縮後障礙物的狀態爲statestate的方案數爲sum[state]sum[state],那麼它的求法就是,把這些障礙物(加上起點和終點)從左下到右上排序,每個階段的方案數累乘起來。設cntcntstatestate中的障礙數,若cntcnt偶數則加上,否則減去。

3,預處理兩兩障礙物之間(包括起點和終點)的方案數,O(k2M)O(k^2M),求解複雜度O(2kk)O(2^kk)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int K=30;
const int MOD=59393;
int n,m,k,tpp,point[K];
ll dis[K][K];
ll pre[MOD],inv[MOD];
ll Ans=0;
struct data
{
	int x,y;
}p[K];
bool cmp(const data a,const data b)
{
	if (a.x==b.x) return a.y<b.y;
	return a.x<b.x;
}
ll qpow(ll x,ll y)
{
	ll ret=1;
	while (y)
	{
		if (y&1) ret=ret*x%MOD;
		y>>=1;
		x=x*x%MOD;
	}
	return ret;
}
void Init()
{
	pre[0]=1;
	for (int i=1;i<MOD;i++) pre[i]=pre[i-1]*i%MOD;
	inv[MOD-1]=qpow(pre[MOD-1],MOD-2);
	for (int i=MOD-2;i>=0;i--) inv[i]=inv[i+1]*(i+1)%MOD;
	return;
}
ll C(ll nn,ll mm)
{
	if (nn<0||mm<0||nn<mm) return 0;
	ll ret;
	ret=pre[nn]*inv[mm]%MOD*inv[nn-mm]%MOD;
	return ret;
}
ll Lucas(ll nn,ll mm)
{
	if (nn<MOD) return C(nn,mm);
	ll ret=Lucas(nn/MOD,mm/MOD)*Lucas(nn%MOD,mm%MOD)%MOD;
	return ret;
}
ll Caldis(int st,int end)
{
	int a1=p[st].x,a2=p[end].x,b1=p[st].y,b2=p[end].y;
	if (b1>b2) return -1;
	ll nn=a2-a1,mm=b2-b1;
	ll ret=0;
	for (int i=0;i<=min(nn,mm);i++) ret=(ret+Lucas(nn+mm-i,i)*Lucas(nn+mm-2*i,nn-i)%MOD)%MOD;
	return ret;
}
int Cnt(int state)
{
	int ret=0;
	tpp=0;
	point[++tpp]=1;
	for (int i=1;i<=k;i++) point[i]=1;
	for (int i=0;i<k;i++) if ((1<<i)&state) ret++,point[++tpp]=i+2;
	point[++tpp]=k+2;
	return ret;
}
int main()
{
//	freopen("1.in","r",stdin);
	Init();
	scanf("%d%d%d",&n,&m,&k);
	for (int i=1;i<=k;i++) scanf("%d%d",&p[i].x,&p[i].y);
	p[k+1]=(data){1,1};
	p[k+2]=(data){n+1,m+1};
	sort(p+1,p+k+3,cmp);
	for (int i=1;i<=k+1;i++)
		for (int j=i+1;j<=k+2;j++) dis[i][j]=Caldis(i,j);
	for (int i=0;i<(1<<k);i++)
	{
		int cnt=Cnt(i);
		ll tmp=1;
		for (int j=1;j<tpp;j++)
		{
			ll tmp2=dis[point[j]][point[j+1]];
			if (tmp2==-1)
			{
				tmp=0;
				break;
			}
			else tmp=tmp*tmp2%MOD;
		}
		if (cnt&1) Ans=(Ans-tmp+MOD)%MOD;
		else Ans=(Ans+tmp)%MOD;
	}
	printf("%lld\n",Ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章