Codeforces Round #536 div2 题解

题目传送门:#536 div2


A. Lunar New Year and Cross Counting

题目描述:

给一张 n ∗ n ( n ≤ 500 ) n*n(n\le 500) nn(n500)的网格图,由’.‘和’X’组成。一个Cross指的是左上右上左下右下及自己都是’X’,统计Cross的数量,两个Cross可以部分重合。
在这里插入图片描述这就是一个Cross。

题目分析:

。。模拟题意就好了,签到题。

Code:

#include<cstdio>
int n,ans;
char a[505][505];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
	for(int i=2;i<n;i++)
		for(int j=2;j<n;j++)
			if(a[i][j]=='X'&&a[i-1][j-1]=='X'&&a[i-1][j+1]=='X'&&a[i+1][j-1]=='X'&&a[i+1][j+1]=='X') ans++;
	printf("%d", ans);
}

B. Lunar New Year and Food Ordering

题目描述:

一个餐馆里面有 n ( n ≤ 1 0 5 ) n(n\le 10^5) n(n105)种菜编号 1 1 1~ n n n,每种菜最初有 a i a_i ai份,每种价格 c i c_i ci
m ( m ≤ 1 0 5 ) m(m\le10^5) m(m105)个顾客依次进店(一位走之后下一位再来),第 j j j个顾客会点 d j d_j dj t j t_j tj号菜。
当顾客点一份 i i i号菜时,设 i i i号菜还剩 r i r_i ri份,送菜的流程是这样的:

  • r i &gt; 0 r_i&gt;0 ri>0,送出一份菜,赚得 c i c_i ci
  • r i = 0 r_i=0 ri=0,会选择当前最便宜(多个最便宜选编号最小)的菜送出一份,假设是 k k k,赚得 c k c_k ck。如果已经没有菜了,顾客就会离开,之前送给这位顾客的菜的钱不能得到。

求出能够赚多少钱。
a i , c i , d j ≤ 1 0 7 a_i,c_i,d_j\le10^7 ai,ci,dj107

题目分析:

把菜品另存一个数组,按照价格排序,然后就是模拟。。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,m,c[maxn],a[maxn],id[maxn],t,d,now=1;
long long sum;
inline bool cmp(int x,int y){return c[x]==c[y]?x<y:c[x]<c[y];}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&c[i]);
	for(int i=1;i<=n;i++) id[i]=i;
	sort(id+1,id+1+n,cmp);
	while(m--)
	{
		sum=0;
		scanf("%d%d",&t,&d);
		if(d<=a[t]) sum+=1ll*d*c[t],a[t]-=d;
		else{
			d-=a[t],sum+=1ll*a[t]*c[t],a[t]=0;
			while(d&&now<=n){
				if(d<=a[id[now]]) sum+=1ll*d*c[id[now]],a[id[now]]-=d,d=0;
				else sum+=1ll*a[id[now]]*c[id[now]],d-=a[id[now]],a[id[now]]=0,now++;
			}
			if(d) sum=0;
		}
		printf("%I64d\n",sum);
	}
}

C. Lunar New Year and Number Division

题目描述:

长度为 n ( n ≤ 3 ∗ 1 0 5 ) n(n\le3*10^5) n(n3105)的序列 a i ( ≤ 1 0 4 ) a_i(\le10^4) ai(104),可以分成若干份(元素可以重排),要求每份长度>=2,且 ∑ \sum 每份的和的平方最小,求这个最小值,保证 n n n为偶数。

题目分析:

显然每一份的元素个数 ≤ 3 \le 3 3
进一步的,每一份只能是2个元素。证明如下:
如果某一份有3个,因为n是偶数,所以一定还有另一份3个,分别设为 { i , j , k } , { x , y , z } \{i,j,k\},\{x,y,z\} {i,j,k},{x,y,z}

  • 不妨设 i i i最小, z z z最大,那么将它们换成 { i , z } , { j , k } , { x , y } \{i,z\},\{j,k\},\{x,y\} {i,z},{j,k},{x,y},减少了 2 i ( j + k ) + 2 z ( x + y ) 2i(j+k)+2z(x+y) 2i(j+k)+2z(x+y),增加了 2 z i 2zi 2zi,而 x + y x+y x+y显然大于 i i i
  • i i i最小, k k k最大同理可得,不再赘述。

每份只能有两个元素,去掉固定的平方之后就是交叉项的乘积,由排序不等式知道排序之后首尾配对是最优的。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,a[300005];
long long ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	for(int i=1;i<=n/2;i++) ans+=(a[i]+a[n-i+1])*(a[i]+a[n-i+1]);
	printf("%I64d",ans);
}

D. Lunar New Year and a Wander

题目描述:

n n n个点 m m m条无向边的图,起始在点1,在纸上写下1,然后沿边走,每走到一个没有走过的点就写在纸上,问n个点都走完后纸上字典序最小的序列是什么。

题目分析:

优先队列,按照现在可以到达的点的编号从小到大排,每次取最小的走,然后又拓展出一些可以走的点放进队列中。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,m,fir[maxn],nxt[maxn<<1],to[maxn<<1],tot,ans[maxn],cnt;
bool vis[maxn];
priority_queue<int,vector<int>,greater<int> >q;
inline void line(int x,int y){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;}
int main()
{
	int x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	q.push(1),vis[1]=1;
	while(!q.empty()){
		x=q.top();q.pop();
		ans[++cnt]=x;
		for(int i=fir[x];i;i=nxt[i]){
			y=to[i];
			if(!vis[y]) q.push(y),vis[y]=1;
		}
	}
	for(int i=1;i<=cnt;i++) printf("%d%c",ans[i],i==n?10:32);
}

E. Lunar New Year and Red Envelopes

题目描述:

Bob要拿红包,有 k k k个红包,时间线是 1 1 1~ n n n,每个红包在 [ s i , t i ] [s_i,t_i] [si,ti]时间内可以拿,拿完之后必须等到 d i d_i di之后才能拿下一个,红包的价值是 w i w_i wi,保证 t i ≤ d i t_i\le d_i tidi
Bob采用贪心策略,每次都选当前能够拿的价值最大的红包,如果有多个则选 d d d最大的。
他的女儿Alice想要阻止Bob,她可以在 x x x时刻阻止Bob,意味着Bob在 x x x时刻不能拿红包,但 x + 1 x+1 x+1时刻及以后会继续他的贪心策略。
Alice最多可以阻止 m m m次,求Bob最后拿到的红包的最小价值和。
n , k ≤ 1 0 5 , m ≤ 200 , 1 ≤ s i , t i , d i ≤ n , w i ≤ 1 0 9 n,k\le10^5,m\le200,1\le s_i,t_i,d_i\le n,w_i\le10^9 n,k105,m200,1si,ti,din,wi109

题目分析:

妥妥的DP,正着来反着来都行,设 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i时刻阻止了 j j j次的最小价值。
每个时刻可以拿的红包用multiset存下来,在 s i s_i si插入, t i t_i ti删除。
正着来就让 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i时刻,转移到 f [ i + 1 ] [ j + 1 ] f[i+1][j+1] f[i+1][j+1]或者 f [ d + 1 ] [ j ] f[d+1][j] f[d+1][j],如果set为空就直接到 f [ i + 1 ] [ j ] f[i+1][j] f[i+1][j]
反着来就让 f [ i ] [ j ] f[i][j] f[i][j]表示后 i i i时刻,由 f [ i + 1 ] [ j − 1 ] f[i+1][j-1] f[i+1][j1]或者 f [ d + 1 ] [ j ] f[d+1][j] f[d+1][j]转移来,如果set为空就由 f [ i + 1 ] [ j ] f[i+1][j] f[i+1][j]转移来

Code:

#include<bits/stdc++.h>
#define maxn 100002
using namespace std;
int n,m,k;
long long f[maxn][201];
struct node{
	int w,d;
	node(int _w=0,int _d=0){w=_w,d=_d;}
	bool operator < (const node &p)const{return w==p.w?d>p.d:w>p.w;}
}tmp;
vector<node>s[maxn],t[maxn];
multiset<node>q;
inline void Min(long long &x,long long y){if(y==-1) return;x=(x==-1?y:min(x,y));}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1,a,b,c,d;i<=k;i++) scanf("%d%d%d%d",&a,&b,&c,&d),s[a].push_back(node(d,c)),t[b].push_back(node(d,c));
	/*for(int i=n;i>=1;i--)
	{
		for(int o=t[i].size()-1;o>=0;o--) q.insert(t[i][o]);
		if(!q.empty()){
			tmp=*q.begin();
			for(int j=0;j<=m;j++) f[i][j]=f[tmp.d+1][j]+tmp.w;
			for(int j=1;j<=m;j++) f[i][j]=min(f[i][j],f[i+1][j-1]);
		}
		else for(int j=0;j<=m;j++) f[i][j]=f[i+1][j];
		for(int o=s[i].size()-1;o>=0;o--) q.erase(q.find(s[i][o]));
	}
	printf("%I64d",f[1][m]);*/
	memset(f,-1,sizeof f);
	for(int j=0;j<=m;j++) f[1][j]=0;
	for(int i=1;i<=n;i++)
	{
		for(int o=s[i].size()-1;o>=0;o--) q.insert(s[i][o]);
		if(q.empty()){
			for(int j=0;j<=m;j++) Min(f[i+1][j],f[i][j]);
			continue;
		}
		tmp=*q.begin();
		for(int j=0;j<=m;j++) if(f[i][j]!=-1){
			Min(f[min(tmp.d+1,n+1)][j],f[i][j]+tmp.w);
			if(j<m) Min(f[i+1][j+1],f[i][j]);
		}
		for(int o=t[i].size()-1;o>=0;o--) q.erase(q.find(t[i][o]));//!!!
	}
	printf("%I64d",f[n+1][m]);
}

PS:用multiset erase的时候删值是会把相等的全部删除的,如果只删一个需要删迭代器(删find)


F. Lunar New Year and a Recursive Sequence

题目描述:

已知 f 1 = f 2 = . . . = f k − 1 = 1 f_1=f_2=...=f_{k-1}=1 f1=f2=...=fk1=1,且有递推式 f i = ∏ j = 1 k f i − j b j   m o d   p f_i=\prod_{j=1}^kf_{i-j}^{b_j}~mod~p fi=j=1kfijbj mod p
其中 p = 998244353 p=998244353 p=998244353
给出 k ( 1 ≤ k ≤ 100 ) , b i k(1\le k\le100),b_i k(1k100),bi,以及两个数 n ( k &lt; n ≤ 1 0 9 ) , m ( 1 ≤ m &lt; p ) n(k&lt;n\le10^9),m(1\le m&lt;p) n(k<n109),m(1m<p),求一个可能的 f k f_k fk使得 f n = m f_n=m fn=m

题目分析:

容易发现 f n f_n fn就是 f k f_k fk的几次幂膜上 p p p,把 f n f_n fn看做 f k a n f_k^{a_n} fkan,递推式可以改写为 a i = ∑ j = 1 k a i − j ∗ b j   m o d   ( p − 1 ) a_i=\sum_{j=1}^ka_{i-j}*b_j~mod~(p-1) ai=j=1kaijbj mod (p1)
然后就是一个妥妥的矩阵加速。
在这里插入图片描述
(图是从CF题解里面截的,上面的 f f f就是我说的 a a a

然后你就得到了 f k T ≡ m   m o d   p f_k^{T}\equiv m~mod~p fkTm mod p T T T是已经求出来的值。
这似乎跟BSGS有点不一样?
原根指标换一换: ( g I ( f k ) ) T ≡ m   m o d   p (g^{I(f_k)})^T\equiv m~mod~p (gI(fk))Tm mod p
把T换到里面: ( g T ) I ( f k ) ≡ m   m o d   p (g^T)^{I(f_k)}\equiv m~mod~p (gT)I(fk)m mod p
然后就是妥妥的BSGS求 I ( f k ) I(f_k) I(fk),快速幂一下就是答案了。忘了BSGS?戳这里

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
const int mod = 998244353, mp = mod-1, G = 3;
int K,n,m;
struct Mat{
	int s[105][105];
	Mat(){memset(s,0,sizeof s);}
	Mat operator * (const Mat &B){
		Mat ret;
		for(int k=1;k<=K;k++)
			for(int i=1;i<=K;i++) if(s[i][k])
				for(int j=1;j<=K;j++) if(B.s[k][j])
					ret.s[i][j]=(ret.s[i][j]+1ll*s[i][k]*B.s[k][j])%mp;
		return ret;
	}
}f,g;
inline int ksm(int a,int b){
	int s=1;
	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) s=1ll*s*a%mod;
	return s;
}
int BSGS(int a,int b,const int p){
	int m=int(sqrt(p)+1);
	static map<int,int>has; has.clear();
	for(int i=0,t=b;i<m;i++) has[t]=i,t=1ll*t*a%p;
	int base=ksm(a,m),now=1;
	for(int i=1;i<=m+1;i++)
		if(has.count(now=1ll*now*base%p)) return i*m-has[now];
	return -1;
}
int main()
{
	scanf("%d",&K);
	for(int i=1;i<=K;i++) scanf("%d",&g.s[K-i+1][K]),g.s[i+1][i]=1;
	f.s[1][K]=1,g.s[K+1][K]=0;
	scanf("%d%d",&n,&m);
	for(n-=K;n;n>>=1,g=g*g) if(n&1) f=f*g;
	int ans=BSGS(ksm(G,f.s[1][K]),m,mod);
	printf("%d\n",ans==-1?-1:ksm(G,ans));
}

Upd:本题的时间复杂度为 O ( k 3 l o g n + p ) O(k^3logn+\sqrt p) O(k3logn+p ),对于线性递推式可以用多项式取模方法优化为 O ( k l o g k l o g n ) O(klogklogn) O(klogklogn),详细做法戳这里

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值