……

口胡(然而有代码)<第四章>

上章回顾:Link

题目计数:\(162\)

完了,刷不动题了/kk。

\(151.\) P4409 [ZJOI2006]皇帝的烦恼

有关集合关系的 dp 题,感觉好神仙,qwq。

还要二分,时间复杂度是 \(\mathcal O(n\log n)\)

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define MAXN 20005
#define read(x) scanf("%d",&x)

int n,a[MAXN];
int minx[MAXN],maxn[MAXN];
int l,r;

int check(int x)
{
	for(int i=2;i<=n;i++)
	{
		minx[i]=max(0,a[i]-(x-(a[1]+a[i-1]-maxn[i-1])));
		maxn[i]=min(a[i],a[1]-minx[i-1]);
	}
	return minx[n]?0:1;
}

int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	a[0]=a[1];
	for(int i=0;i<n;i++) l=max(l,a[i]+a[i+1]);
	r=2*l,minx[1]=maxn[1]=a[1];
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	return 0;
}

\(152.\) AT4636 When I hit my pocket...

考虑贪心。

我们发现把饼干换成钱却不换回来一定是很亏的...

只有把钱换成饼干而且增值的时候有用,那就等价成花两步,增加 \(b-a\) 个饼干,显然当 \(b-a>2\) 时我们就可以贪心的只选这一步了。

但是我们先要花 \(a-1\) 步才能有基准的 \(a\) 个饼干,对于 \(k\leq a\)(多一步没什么卵用)的,要特判。

剩下的奇偶也有判断,这就是一个恶心的分类讨论啊...

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define ll long long
#define read(x) scanf("%lld",&x)

ll k,a,b;
ll x;

int main()
{
	read(k),read(a),read(b);
	if(k<=a||b-a<=2ll) return printf("%lld\n",k+1ll),0;
	ll x=k-a+1;
	if(x&1ll) printf("%lld\n",a+((x-1)>>1)*(b-a)+1ll);
	else printf("%lld\n",a+(x>>1)*(b-a));
	return 0;
}

\(153.\) AT4637 Ears

这个题好巨啊 Orz

我们先来考虑什么时候可以构造出完整的路线。

一个比较显然的结论是中间不能有 \(0\)

容易猜到和数字奇偶性有关。

考虑一个偶数,他的性质的从一侧进,从同侧出;奇数相反。

于是考虑经过偶数的走法

如果一股劲走完这些,显然不能去另一边了。

如果走不完,那得保证最左边是偶数,然后才能实现转向回来走完。

我们发现如果是多个奇偶块交错,那么显然奇数一侧进另一侧出的性质只能用一次,不能支持回来了,所以合法的序列一定是:

\(0\),奇数,偶数,奇数,\(0\)

或者是他的子序列。

这里的这些数可能是一段,现在如果你在想 \(\mathcal O(n^4)\) dp(谔谔,好像是暴力),那只能像我一样坐以待毙了/kk

我们设 \(dp_{i,j}\) 为到第 \(i\) 个点,已经是第 \(j\) 段的最小步数,转移就比较显然了。

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%lld",&x)
#define int long long
#define MAXN 200005

int n;
int dp[MAXN][6],a[MAXN];

signed main()
{
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=0;i<=4;i++) dp[0][i]=0;
	for(int i=1;i<=n;i++) for(int j=0;j<=4;j++) dp[i][j]=1ll<<62;
	for(int i=1;i<=n;i++)
	{
		if(!a[i])
		{
			dp[i][0]=dp[i-1][0];
			for(int k=0;k<=4;k++) dp[i][4]=min(dp[i][4],dp[i-1][k]);
			for(int k=0;k<=1;k++) dp[i][1]=min(dp[i][1],dp[i-1][k]+2);
			for(int k=0;k<=3;k++) dp[i][3]=min(dp[i][3],dp[i-1][k]+2);
			for(int k=0;k<=2;k++) dp[i][2]=min(dp[i][2],dp[i-1][k]+1);
		}
		else if(a[i]&1)
		{
			dp[i][0]=dp[i-1][0]+a[i];
			for(int k=0;k<=4;k++) dp[i][4]=min(dp[i][4],dp[i-1][k]+a[i]);
			for(int k=0;k<=1;k++) dp[i][1]=min(dp[i][1],dp[i-1][k]+1);
			for(int k=0;k<=3;k++) dp[i][3]=min(dp[i][3],dp[i-1][k]+1);
			for(int k=0;k<=2;k++) dp[i][2]=min(dp[i][2],dp[i-1][k]);
		}
		else
		{
			dp[i][0]=dp[i-1][0]+a[i];
			for(int k=0;k<=4;k++) dp[i][4]=min(dp[i][4],dp[i-1][k]+a[i]);
			for(int k=0;k<=1;k++) dp[i][1]=min(dp[i][1],dp[i-1][k]);
			for(int k=0;k<=3;k++) dp[i][3]=min(dp[i][3],dp[i-1][k]);
			for(int k=0;k<=2;k++) dp[i][2]=min(dp[i][2],dp[i-1][k]+1);
		}
	}
	int ans=1ll<<62;
	for(int i=0;i<=4;i++) ans=min(ans,dp[n][i]);
	printf("%lld\n",ans);
	return 0;
}

\(154.\) AT5749 Subarray Sum

一道比较简单的构造题。

考虑把前 \(m\) 个数都设成 \(s\),在都是正数的前提下,不会出现包含他们的合法子序列,然后剩下的越离谱越好。

一种比较自然地想法是 \(s\) 比较小时,剩下的全是 \(10^9\),反之,全是 \(1\)

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 100005

int n,m,s;

int main()
{
	read(n),read(m),read(s);
	for(int i=1;i<=m;i++) printf("%d ",s);
	if(s>800000000)
		for(int i=m+1;i<=n;i++) printf("%d ",1);
	else for(int i=m+1;i<=n;i++) printf("%d ",1000000000);
	return puts(""),0;
}

\(155.\) AT5750 Swap and Flip

这题怎么一个比一个神仙啊,哦好不是,是我太菜了/ll/ll

开始的想法是用二进制串来代表每个卡片是红色还是蓝色,然后却发现如果数字是重的,就会很麻烦以至于不会做就弃了。

不过一个简单的性质还是容易发现的,就是移动了偶数次就是红面,反之是蓝面(哎,这个 AtCoder 怎么这么爱奇偶性啊......)

新加入一张卡要移动多少次?

我们如果每次从小到大加入,最后的这一张后面早被选过的卡即会产生逆序对,这就是要 swap 的次数,然后这题就做完了。

容易状压,时间复杂度是 \(\mathcal O(2^n n^2)\)

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define read(x) scanf("%d",&x)
#define inf 1000000000

int n,x[20][2];
int dp[1000005][20];

int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(x[i][0]);
	for(int i=1;i<=n;i++) read(x[i][1]);
	for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++) dp[i][j]=inf;
	for(int i=0;i<n;i++) dp[1<<i][i]=0;
	dp[0][0]=0;
	for(int s=1;s<(1<<n);s++)
	{
		int op=0;
		for(int j=0;j<n;j++)
		{
			if((1<<j)&s) op++;
		}
		for(int i=0;i<n;i++)
		{
			if(((1<<i)&s)==0) continue;
			int num=0;
			for(int j=i+1;j<n;j++) if((1<<j)&s) num++;
			//要移动的次数及时逆序对数,上面求的是新增的逆序对数 
			for(int j=0;j<n;j++)
			{
				if(j==i) continue;
				if(((1<<j)&s)==0) continue;
				int t=abs(j-op+2)&1;//蓝面返回1,下同 
				int r=abs(i-op+1)&1;
				if(x[j+1][t]>x[i+1][r]) continue;
				dp[s][i]=min(dp[s][i],dp[s^(1<<i)][j]+num);
			}
		}
	}
	int ans=inf;
	for(int i=0;i<n;i++) ans=min(ans,dp[(1<<n)-1][i]);
	if(ans>=inf) puts("-1");
	else printf("%d\n",ans);
	return 0; 
}

\(156.\) P4878 [USACO05DEC]Layout G

容易看出是一道差分约束的题目,但是为了判断是否有可行排列方案,要先建立超级源汇点,判断负环,然后再从一跑最短路。

注意两次 SPFA 之间的清空,时间复杂度为 \(\mathcal O(kn)\)

#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;

#define read(x) scanf("%lld",&x)
#define int long long
#define MAXN 100005

int n,a,b;
int u,v,w;
struct node
{
	int to,nxt,w;
}e[MAXN*4];
int head[1005],cnt=0;
queue<int>q;
int vis[MAXN],ma[MAXN];
int dis[MAXN],len[MAXN];
int f=0;

void add(int u,int v,int w)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].w=w;
	head[u]=cnt;
}

void SPFA(int s)
{
	q.push(s),vis[s]=1,dis[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		ma[u]=1;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int j=e[i].to;
			if(dis[j]>dis[u]+e[i].w)
			{
				dis[j]=dis[u]+e[i].w;
				len[j]=len[u]+1;
				if(!vis[j]) vis[j]=1,q.push(j);
				if(len[j]>=n+4){f=-1;return;}
			}
		}
	}
}

signed main()
{
	read(n),read(a),read(b);
	for(int i=1;i<=a;i++)
	{
		read(u),read(v),read(w);
		if(u>v) swap(u,v);
		add(u,v,w),add(v,u,0);
	}
	for(int i=1;i<=b;i++)
	{
		read(u),read(v),read(w);
		if(u>v) swap(u,v);
		add(v,u,-w);
	}
	for(int i=1;i<=n;i++) add(0,i,0);
	for(int i=1;i<n;i++) add(i+1,i,0);
	for(int i=0;i<=n;i++) dis[i]=1ll<<60,vis[i]=0,len[i]=0;
	SPFA(0);
	if(f<0) return puts("-1"),0;
	else if(!ma[n]) return puts("-2"),0;
	for(int i=1;i<=n;i++) dis[i]=1ll<<60,vis[i]=0,len[i]=0,ma[i]=0;
	f=0;
	SPFA(1);
	if(f<0) puts("-1");
	else if(!ma[n]) puts("-2");
	else printf("%lld\n",dis[n]);
	return 0;
}

\(157.\) AT4704 Snuke the Wizard

发现无论如何移动相对顺序不变,二分最里面的被移出去的位置即可。

#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;

#define MAXN 200005

int n,m,sum;
char s[MAXN];
char op[MAXN],rt[MAXN];

int check1(int x)
{
	int opt=x;
	char now=s[x];
	for(int i=1;i<=m;i++)
	{
		if(now==op[i])
		{
			if(rt[i]=='L')
			{
				opt--;
				now=s[opt];
				if(!opt) return 1;
			}
			else 
			{
				opt++;
				now=s[opt];
				if(opt>n) return 0;
			}
		}
	}
	return 0;
}

int check2(int x)
{
	int opt=x;
	char now=s[x];
	for(int i=1;i<=m;i++)
	{
		if(now==op[i])
		{
			if(rt[i]=='L')
			{
				opt--;
				now=s[opt];
				if(!opt) return 0;
			}
			else 
			{
				opt++;
				now=s[opt];
				if(opt>n) return 1;
			}
		}
	}
	return 0;
}

int main()
{
	scanf("%d%d",&n,&m),sum=n;
	scanf("%s",s+1);
	for(int i=1;i<=m;i++)
	{
		cin>>op[i]>>rt[i];
	}
	int l=1,r=n;
	if(!check1(1)) l=0;
	else
	{
		while(l<r)
		{
			int mid=(l+r)>>1;
			if(check1(mid)) l=mid+1;
			else r=mid;
		}
		if(!check1(l)) l--;
	}
	sum-=l;
	l++,r=n;
	if(check2(n))
	{
		while(l<r)
		{
			int mid=(l+r)>>1;
			if(check2(mid)) r=mid;
			else l=mid+1;
		}
		sum=sum-(n-r+1);
	}
	printf("%d\n",sum);
	return 0;
}

\(158.\) P2182 翻硬币

半退役蒟蒻水一个不正常写法。

我们发现我们不关心一个位置具体翻的次数,只关心他翻了奇数次还是偶数次。

于是再看这个数据范围,思路就比较明了了。

当然要先求出偶数次翻转的位置有 \(s\) 个。

\(dp_{i,j}\) 为 翻了 \(j\) 次,有 \(i\) 个硬币被翻了偶数次的方案数。

显然有 \(dp_{n,0}=1\)

我们根据小学组合知识可以得到转移方程。

当翻了 \(j\) 个,其中有 \(l\) 个从前是翻了偶数次的,可以得到这一操作的贡献为:

\[dp_{i+m-2l,j+1}\xleftarrow{+} dp_{i,j}\times \dbinom{i}{l}\times\dbinom{n-i}{m-l} \]

然后你问:这样做之后 \(dp_{s,k}\) 就是答案吗?

当然不是,我们 dp 是是乱选的,而最后是有序的。

我们需要除以所有排法,就是把 \(s\) 个偶数和 \(n-s\) 个奇数排列,奇数和奇数,偶数和偶数之间没有差别。

根据简单的组合知识能够知道,答案是 \(\dfrac{dp_{s,k}}{\dbinom{n}{s}}\),模意义下除法要写逆元哦!

时间复杂度是 \(\mathcal O(n^2k)\)

#include"iostream"
#include"cmath"
#include"cstdio"
using namespace std;

#define read(x) scanf("%d",&x)
#define MAXN 105
#define ll long long
#define MOD 1000000007

int n,m,k,s;
ll C[MAXN][MAXN],dp[MAXN][MAXN];
char c[MAXN],cc[MAXN];

ll quickpow(ll a,ll b)
{
	ll ans=1,base=a;
	while(b)
	{
		if(b&1) ans=ans*base%MOD;
		base=base*base%MOD;
		b>>=1;
	}
	return ans%MOD;
}

int main()
{
	read(n),read(k),read(m);
	scanf("%s",c),scanf("%s",cc);
	for(int i=0;i<n;i++) if(c[i]==cc[i]) s++;
	for(int i=0;i<=100;i++) C[i][0]=1ll;
	for(int i=1;i<=100;i++)
	{
		for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	dp[n][0]=1ll;
	for(int i=0;i<k;i++)
	{
		for(int j=0;j<=n;j++)
		{
			for(int l=0;l<=min(j,m);l++)
			{
				if(n-j<m-l) continue;
				dp[j+m-2*l][i+1]=(dp[j+m-2*l][i+1]+dp[j][i]*C[j][l]%MOD*C[n-j][m-l]%MOD)%MOD;
			}
		}
	}
	printf("%lld\n",dp[s][k]*quickpow(C[n][s],MOD-2)%MOD);
	return 0;
}

\(158.\) P2512 [HAOI2008]糖果传递

典型的环形均分纸牌问题,具体可见 Link of P2125

是中位数模型的经典应用,可以在 \(\mathcal O(n)\sim O(n\log n)\) 内解决。

排个序多简单我才不写线性呢/fad。

#include"algorithm"
#include"iostream"
#include"cstdio"
using namespace std;

#define ll long long 
#define MAXN 5000005

int n;
ll a[MAXN],s[MAXN],c[MAXN],p[MAXN];
ll x[MAXN];
ll sum=0,now;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i];
	sum=sum/(ll)n;
	for(int i=1;i<=n;i++) s[i]=sum-a[i];
	for(int i=2;i<=n;i++) c[i]=c[i-1]+s[i],p[i]=c[i];
	sort(c+1,c+n+1),sum=0;
	now=c[(n+1)/2];
	for(int i=1;i<=n;i++) sum=sum+abs(now-p[i]);
	printf("%lld\n",sum);
	return 0;
}

\(159.\) P2115 [USACO14MAR]Sabotage G

显然想到的是一段子区间,预处理前缀和可以 \(\mathcal O(1)\) 查询。

这个复杂度确实不错,给我们乱搞留下了充足的时间。

容易想到相关参量只有 \(l,r\) ,所以考虑模拟退火

注意不要把初温设的太大,更不要把末温设的太小,这样都是白费时间。

然后你发现还是过不了(,把接受概率调小一些就可以了。

好像我 rp 不太行,跑 \(0.9 \;s\) 都会错,所以改成 \(0.95\) 秒就可以过了!

#include"algorithm"
#include"iostream"
#include"cstdio"
#include"ctime"
#include"cmath"
using namespace std;

#define MAXN 100005
#define read(x) scanf("%d",&x)

int n,a[MAXN];
int x,y,L,R;
int l=2,r;
int s[MAXN];
double ans=1000000000000.00;
double t;

inline double check(int l,int r)
{
	return (double)(s[n]-s[r]+s[l-1])/(n-r+l-1);
}

inline void SA()
{
	double T=2*n,delta=0.994,T0=0.05;
	while(T>=T0)
	{
		L=x+((1.0*rand()/RAND_MAX*2)-1)*T;
		R=y+((1.0*rand()/RAND_MAX*2)-1)*T;
		L=min(L,r),L=max(l,L),R=max(L,R),R=min(R,r);
		double op=check(L,R);
		if(op<ans) x=L,y=R,ans=op;
		else if(rand()<=exp((ans-op)/T*100000000)*RAND_MAX) x=L,y=R;
		T*=delta;
	}
}

inline void work(){while((clock()-t)/CLOCKS_PER_SEC<0.95) SA();}

int main()
{
	t=clock();
	srand((int)time(0)),srand(19260817),srand((int)time(0));
	read(n),r=n-1;
	for(register int i=1;i<=n;i++) read(a[i]),s[i]=s[i-1]+a[i];
	work();
	printf("%.3lf\n",ans);
	return 0;
}

\(160.\) P3594 [POI2015]WIL-Wilcze doły

考虑二分。

发现删的越多越好,所以删除只有 \(n-d+1\) 种可能。

比较套路的是二分左端点,前缀和处理一下区间和。

现在的问题是如何确定完全包含在这个区间内的最大的删除量。

发现对于每个区间,内含的可删除区间只有 \(l-d+1\) 个,而且是一个定值,所以可以单调队列。

另外这题比较坑,线段树常数大并且带 \(\log\),而 st 表在 \(2e6\)long long 下无法施展,单调队列就成了首选,时间复杂度是 \(\mathcal O(n\log n)\)

#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;

#define readl(x) scanf("%lld",&x)
#define read(x) scanf("%d",&x)
#define MAXN 2000005
#define ll long long

int n,d,loc[MAXN],head,tail;
ll p,a[MAXN],q[MAXN],c[MAXN],s[MAXN];

bool check(int l)
{
	head=0,tail=1;
	int k=l-d+1;
	for(int i=1;i<=n-d+1;i++)
	{
		while(head>=tail&&c[i]>=q[head]) head--;
		q[++head]=c[i],loc[head]=i;
		if(i-k+1>loc[tail]) tail++;
		if(i>=k)
		{
			ll now=s[i+d-1]-s[i+d-l-1]-q[tail];
			if(now<=p) return true;
		}
	}
	return false;
}

int main()
{
	read(n),readl(p),read(d);
	for(int i=1;i<=n;i++) readl(a[i]);
	for(int i=1;i<=d;i++) c[1]+=a[i];
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	for(int i=2;i<=n-d+1;i++) c[i]=c[i-1]-a[i-1]+a[i+d-1];
	int l=d,r=n;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) l=mid+1;
		else r=mid;
	}
	if(!check(l)) l--;
	printf("%d\n",l);
	return 0;
}

\(161.\) CF854B Maxim Buys an Apartment

找规律题。

考虑没别影响到的点可以是放房子也可以有后面的点影响到,然后找一下规律发现 \(k\) 个房子最大影响长度为 \(3k\),能卖的是 \(2k\)

注意有很多特判qwq。

#include"iostream"
#include"cstdio"
using namespace std;

#define read(x) scanf("%lld",&x)
#define int long long

int n,k;

signed main()
{
	read(n),read(k);
	if(k==n||(!k)) return printf("0 0\n"),0;
	if(3ll*k>=n)
	{
		printf("%lld %lld\n",1ll,n-k);
		return 0;
	}
	printf("%lld %lld\n",1ll,2*k);
	return 0;
}

\(162.\) P1472 [USACO2.3]奶牛家谱 Cow Pedigrees

\(dp_{i,j}\) 为选了 \(i\) 个点,最多 \(j\) 层的方案数,于是可以做到比较显然的 \(\mathcal O(n^2k)\) 计算。

就是这个设法十分神仙/kk

最后输出时差分一下即可。

#include"iostream"
#include"cmath"
#include"cstdio"
using namespace std;

#define MAXN 100
#define read(x) scanf("%d",&x)
#define MOD 9901

int n,m;
int dp[MAXN<<1][MAXN];

int main()
{
	read(n),read(m);
	for(int i=1;i<=m;i++) dp[1][i]=1;
	for(int j=1;j<=m;j++)
	{
		for(int i=3;i<=n;i+=2)
		{
			for(int k=1;k<i;k+=2)
			{
				dp[i][j]=(dp[i][j]+dp[k][j-1]*dp[i-1-k][j-1]%MOD)%MOD;
			}
		}
	}
	printf("%d\n",(dp[n][m]-dp[n][m-1]+MOD)%MOD);
	return 0;
}
posted @ 2020-11-30 08:55  童话镇里的星河  阅读(147)  评论(0编辑  收藏  举报