2017六省联考部分题目整理【期末考试,寿司餐厅,组合数问题,分手是祝愿】

【期末考试】

题目链接

题意分析

首先 做这道题的时候 我还是想感谢一下

看了看这套题目的数据范围 发现其实这是一道枚举题

我们发现一个学生会产生不愉快度仅仅会和最晚公布成绩的课程的公布时间有关

所以我们考虑枚举最晚全部课程公布的时间

对于当前枚举的时间t 我们计算一下已经超过这个时间的课程总数 以及 没有超过这个这个时间的课程总数

当然还有 这两类课程跟当前枚举t所相差的时间总和x以及y

然后的话 我们分析一下A以及B

如果A<B的话 我们就考虑x以及y

如果x≤y 那么我们可以把超过的全部提前

如果x>y 那么我们还需要把剩下的用B来补充

如果A≥B的话 我们就直接用B提前就可以了

然后再看看这样的话依然存在学生的不愉快度

不断比较取最小值就可以了

还有这道题的答案要使用unsigned long long 否则会炸 只有90分

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#define N 1000080
#define ll unsigned long long
using namespace std;
template<typename T>void read(T &_)
{//鬼畜快读
	T __=0,___=1;char ____=getchar();
	while(!isdigit(____)) {if(____=='-') ___=0;____=getchar();}
	while(isdigit(____)) {__=(__<<1)+(__<<3)+____-'0';____=getchar();}
	_=___ ? __:-__;
}
ll n,m;
ll ans=1e18;
ll sa,sb,sc,sx,sy,sz;
//sa是已经超过当前枚举时间的课程的数量
//sx是已经超过当前枚举时间的课程的超过时间总和
//sb是未超过当前枚举时间的课程的数量
//sx是未超过当前枚举时间的课程的超过时间总和
//sc是希望在当前时间t之前就是公布全部成绩的学生总数
//sz是希望在当前时间t之前就是公布全部成绩的学生的时间差
ll cdy,wzy,zjz;
ll tim[N],bel[N];
ll vis1[N],vis2[N];
int main()
{
	read(cdy);read(wzy);read(zjz);
	read(n);read(m);
	for(ll i=1;i<=n;++i) read(tim[i]);
	for(ll i=1;i<=m;++i) read(bel[i]);
	for(ll i=1;i<=n;++i)
	{//由于是枚举时间 所以我使用权值数组进行记录
		vis2[tim[i]]++;sz+=tim[i];
	}
	for(ll i=1;i<=m;++i)
	{
		vis1[bel[i]]++;sy+=bel[i];
	}
	sc=n;sb=m;    
	for(ll i=100000;i;--i)
	{
		ll le=0,ri=0;
		sx+=i*vis1[i];sa+=vis1[i];
		sy-=i*vis1[i];sb-=vis1[i];
		sz-=i*vis2[i];sc-=vis2[i];
		if(!sa) continue;
		ll nowtmp=0;
		if(cdy<wzy)
		{
			le=sx-i*sa;ri=i*sb-sy;
			nowtmp+=min(le,ri)*cdy;
			le-=min(le,ri);
			if(le) nowtmp+=le*wzy;//不足的我们需要使用B来补充
		}
		else
		{
			le=sx-i*sa;
			nowtmp+=le*wzy;
		}
		nowtmp+=(i*sc-sz)*zjz;
		ans=min(ans,nowtmp);
	}
	cout<<ans<<endl;
	return 0;
} 

【寿司餐厅】

题目链接

题意分析

这里好多大佬都说这道题是最大权闭合子图 为什么这么说 且听我慢慢道来

我们考虑一下 如果我们选择了一段寿司\([i,j]\) 那么我们是不是也就相当于选择了\([i+1,j]\)以及\([i,j-1]\)

前者对于后者是包含关系 如果选择前者必须选择后者

其次 题意里面 每一个\(d_{ij}\)都只能被选择一次 而且有正有负

这不正好就是最大权闭合子图的意义吗?

关于最大权闭合子图 我之前就已经整理过了 【最大权闭合子图】

但是这道题还多了些东西 就是代号以及代号对应的花费

我们就直接在建图的时候就把最后只有一个的寿司区间同对应的代号连接 并且无法割断

这就是大体的建图 接下来是具体怎么见图的

具体建图过程

我们还是建立超级源S以及超级汇T

1.对于一个区间[i,j]

如果\(d_{ij}>0\) 按照最大权闭合子图的建图要求 我们将其与S相连 容量为\(d_{ij}\)

如果\(d_{ij}<0\) 按照最大权闭合子图的建图要求 我们将其与T相连 容量为\(-d_{ij}\)

2.对于一个区间[i,j]

我们将ta与[i+1,j]以及[i,j-1]相连 容量是inf 意味着无法割断

3.对于一个寿司i 我们假设ta对应的代号就是x

我们将ta对应的区间[i,i]同x代号相连 容量是inf 也意味着无法割断

4.对于一个代号x

我们将ta与T相连 容量就是\(mx^2\) 因为这选择的代价

5.对于选择代号为x的寿司 代价为\(mx^2+cx\)

c就是选择的代号为x的寿司的数量 这个东西我们可以考虑从对应寿司的收益d减去

这样如果我们选择了ta的话 也就是自行承担了代价

好了 图我们已经建好了 网络流怎么跑就不用我说了吧

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<queue>
#define N 1008611
#define M 110
#define inf 2147483600
using namespace std;
int n,m,tot=1,cnt,k,S,T;
long long ans;
int to[N],nex[N],head[N],w[N],cur[N];
int bel[M],id[M][M],num[M][M],res[M],rak[M];
int dep[N];
queue<int> que;
void add(int x,int y,int z)
{to[++tot]=y;nex[tot]=head[x];head[x]=tot;w[tot]=z;
swap(x,y);to[++tot]=y;nex[tot]=head[x];head[x]=tot;w[tot]=0;}
bool bfs()
{
	for(int i=1;i<=T;++i) dep[i]=-1;
	dep[S]=1;que.push(S);
	for(;!que.empty();)
	{
		int u=que.front();que.pop();
		for(int i=head[u];i;i=nex[i])
		{
			int v=to[i];
			if(w[i]>0&&dep[v]==-1)
			{
				dep[v]=dep[u]+1;
				que.push(v); 
			}
		}
	}
	return dep[T]!=-1;
}
int dfs(int now,int res)
{
	if(now==T||!res) return res;
	for(int &i=cur[now];i;i=nex[i])
	{
		int v=to[i];
		if(w[i]>0&&dep[v]==dep[now]+1)
		{
			int have=dfs(v,min(res,w[i]));
			if(have)
			{
				w[i]-=have;w[i^1]+=have;
				return have;
			}
		}
	}
	return 0;
}
void Dinic()
{
	while(bfs())
	{
		for(int i=1;i<=T;++i) cur[i]=head[i];
		int d=dfs(S,inf);
		while(d) ans-=d,d=dfs(S,inf);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&bel[i]),res[i]=bel[i];
	sort(res+1,res+n+1);k=unique(res+1,res+n+1)-res-1;
        //这里的代号并不是从1开始的而且可能非连续
        //所以为了节省空间(或者纯属闲的) 我就给代号来了个离散化
	for(int i=1;i<=n;++i)
	rak[i]=lower_bound(res+1,res+k+1,bel[i])-res;
	for(int i=1;i<=n;++i)
	 for(int j=i;j<=n;++j)
	  scanf("%d",&num[i][j]);
	for(int i=1;i<=n;++i)
	 for(int j=i;j<=n;++j)
	  id[i][j]=++cnt;
	S=cnt+k+1;T=cnt+k+2; 
	for(int i=1;i<=k;++i)
	add(cnt+i,T,m*res[i]*res[i]);//建边要素4
	for(int i=1;i<=n;++i)
	add(id[i][i],cnt+rak[i],inf),num[i][i]-=bel[i];//建边要素3以及5
	for(int i=1;i<=n;++i)
	 for(int j=i;j<=n;++j)
	 {
	 	if(num[i][j]>0) add(S,id[i][j],num[i][j]),ans+=num[i][j];
	 	else add(id[i][j],T,-num[i][j]);//建边要素1
	 	if(i<j)
	 	{
	 		add(id[i][j],id[i+1][j],inf);//建边要素2
	 		add(id[i][j],id[i][j-1],inf);
		}
	 }  
	Dinic(); //愉快的跑最大流
	printf("%lld\n",ans);
	return 0;
}

组合数问题

这道题求的就是

\[(\sum_{i=0}^{n-1}C_{nk}^{ik+r})\%p \]

我们可以发现的是 相对于之前的组合数问题 对答案有贡献的\(C_{n}^{m}\)存在\(m≡r(mod\ k)\)

而且这道题的k以及r的规模都比较小 所以我们可以搞一些事情

我们定义f[i][j]

\[f[i][j]\ =(\ \sum C_{i}^{x})\%p,x≡j(mod\ k) \]

这样的话 按照组合数的一个求解方式

\[f[i][j]=(f[i-1][j]+f[i-1][(j-1+k)\%k])\%p \]

我们惊异的发现这就可以使用矩阵转移了

\[\begin{bmatrix} {f[i-1][0]}&{f[i-1][1]}&{\cdots}&{f[i-1][k-1]} \end{bmatrix} \begin{bmatrix} {1}&{1}&{0}&{0}&{0}&{\cdots}&{0}&{0}\\ {0}&{1}&{1}&{0}&{0}&{\cdots}&{0}&{0}\\ {0}&{0}&{1}&{1}&{0}&{\cdots}&{0}&{0}\\ {0}&{0}&{0}&{1}&{1}&{\cdots}&{0}&{0}\\ {0}&{0}&{0}&{0}&{1}&{\cdots}&{0}&{0}\\ {\vdots}&{\vdots}&{\vdots}&{\vdots}&{\vdots}&{\ddots}&{\vdots}&{\vdots}\\ {0}&{0}&{0}&{0}&{0}&{\cdots}&{1}&{1}\\ {1}&{0}&{0}&{0}&{0}&{\cdots}&{0}&{1}\\ \end{bmatrix} = \begin{bmatrix} {f[i][0]}&{f[i][1]}&{\cdots}&{f[i][k-1]} \end{bmatrix} \]

然后的话 我们使用矩阵快速幂来优化就可以了

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
long long mod;
int n,k,r;
struct Node
{
	long long val[55][55];
	friend Node operator *(const Node &A,const Node &B)
	{
		Node tmp;memset(tmp.val,0,sizeof tmp.val);
		for(int i=0;i<k;++i)
		 for(int j=0;j<k;++j)
		  for(int x=0;x<k;++x)
		   tmp.val[i][j]=(tmp.val[i][j]+A.val[i][x]*B.val[x][j]%mod)%mod;
		return tmp;   
	}
}ans,res;
Node qpow(Node x,long long y)
{
	Node tmp;memset(tmp.val,0,sizeof tmp.val);
	for(int i=0;i<k;++i) tmp.val[i][i]++;
	for(;y;y>>=1,x=x*x) if(y&1) tmp=tmp*x;
	return tmp;
}	
int main()
{
	scanf("%d%lld%d%d",&n,&mod,&k,&r);
	memset(ans.val,0,sizeof ans);memset(res.val,0,sizeof res.val);
	ans.val[0][0]=1LL;
	for(int i=0;i<k;++i)
	res.val[i][i]++,res.val[i][(i+1)%k]++;
	ans=ans*qpow(res,(long long)n*(long long)k);
	printf("%lld\n",ans.val[0][r]);
	return 0;
}

【分手是祝愿】

题目链接

题意分析

这道题目的提议我们大致分析一下

我们假设灭掉全部灯的最少次数是cnt

我们可以发现的是 最优的操作就是从n到1 当前灯亮着的话就按照题意灭掉ta以及ta的约数灯

而且仔细思考思考的话 操作顺序是可以更换的

暂且不讨论最后阶乘的事

如果cnt≤k 按照要求 答案就是cnt

如果cnt≥k的话 那就有意思了

我们设dp[i]为当前需要操作次数为i到I-1的期望操作次数 因为是随机操作 所以从i次到i-1次不一定是1

看我这数组命名的就是知道我想要干什么了 然后我们可以进行状态转移

\[dp[i]=\frac{i}{n}+\frac{n-i}{i}(dp[i]+dp[i+1]+1) \]

左边的\(\frac{i}{n}\)我们可以明白 就是一不小心中奖了灭对了

右边的就是说 我们没能进行正确操作 白瞎了一次 而且需要操作次数从i变成了i+1

那么在从i+1变成i 从i变成i-1 就一共需要dp[i+1]+dp[i]+1次

但是这个状态转移方程很显然不是我们最终想要的 所以我们就考虑一下转化

\[dp[i]=\frac{n+(n-i)*dp[i+1]}{i} \]

那么最后的答案就是

\[\sum_{i=k+1}^{cnt}dp[i]+k \]

然后乘上一个阶乘就完事了

顺道提醒一句 不开long long见祖宗

CODE:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define N 100080
#define mod 100003
using namespace std;
int n,k,cnt;
long long dp[N],inv[N];
long long ans;
int num[N];
vector<int> G[N];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i) scanf("%d",&num[i]);
	for(int i=1;i<=n;++i)
	 for(int j=i;j<=n;j+=i)
	  G[j].push_back(i);
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;++i)
	inv[i]=(mod-mod/i)*inv[mod%i]%mod;  	
	for(int i=n;i;--i)
	{
		if(num[i])
		{
			++cnt;
			for(int j=0;j<(int)G[i].size();++j)
			num[G[i][j]]^=1;
		}
	}  
	if(cnt<=k) ans=cnt;
	else
	{
		for(int i=n;i;--i)
		dp[i]=((long long)n+(long long)(n-i)*dp[i+1]%mod)*inv[i]%mod;
		for(int i=cnt;i>k;--i) ans=(ans+dp[i])%mod;
		ans=(ans+k)%mod;
	}
	for(int i=1;i<=n;++i) ans=(ans*(long long)i)%mod;
	printf("%lld\n",ans);
	return 0;
} 
posted @ 2020-11-28 19:41  tcswuzb  阅读(142)  评论(0编辑  收藏  举报