题目传送门:#536 div2
A. Lunar New Year and Cross Counting
题目描述:
给一张
n
∗
n
(
n
≤
500
)
n*n(n\le 500)
n∗n(n≤500)的网格图,由’.‘和’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(n≤105)种菜编号
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(m≤105)个顾客依次进店(一位走之后下一位再来),第
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 > 0 r_i>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,dj≤107
题目分析:
把菜品另存一个数组,按照价格排序,然后就是模拟。。
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(n≤3∗105)的序列 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
ti≤di。
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,k≤105,m≤200,1≤si,ti,di≤n,wi≤109
题目分析:
妥妥的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][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]转移来
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=...=fk−1=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=1∏kfi−jbj mod p
其中
p
=
998244353
p=998244353
p=998244353
给出
k
(
1
≤
k
≤
100
)
,
b
i
k(1\le k\le100),b_i
k(1≤k≤100),bi,以及两个数
n
(
k
<
n
≤
1
0
9
)
,
m
(
1
≤
m
<
p
)
n(k<n\le10^9),m(1\le m<p)
n(k<n≤109),m(1≤m<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=1∑kai−j∗bj mod (p−1)
然后就是一个妥妥的矩阵加速。
(图是从CF题解里面截的,上面的
f
f
f就是我说的
a
a
a)
然后你就得到了
f
k
T
≡
m
m
o
d
p
f_k^{T}\equiv m~mod~p
fkT≡m 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))T≡m 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),详细做法戳这里