【前言】
原题是
COCI
\text{COCI}
COCI的,然而懒得找。
T1
【题目】
一个
n
×
m
n\times m
n×m的二维矩阵,第
i
i
i行第
j
j
j列的价值是
w
i
,
j
w_{i,j}
wi,j,初始起点在第
A
A
A行第
B
B
B列的格子,要走
K
K
K步,最终回到起点,每一步可以走四相邻的格子。每进入一个格子可以得到该格子的价值,可以重复进入相同格子,而且重复进入可以得到当前格子。求能获得最大价值。
n
,
m
≤
100
,
k
,
w
i
,
j
≤
1
0
9
n,m\leq 100,k,w_{i,j}\leq 10^9
n,m≤100,k,wi,j≤109,且
k
k
k是偶数
【解题思路】
我们可以
YY
\text{YY}
YY一下答案的组成,那么一定是沿着一条路径走,然后反复横跳,再沿着路径走回去。
因为所有路径最长只有
n
×
m
n\times m
n×m,于是我们可以暴力迭代前
n
×
m
n\times m
n×m步,剩下的步数直接计算即可。
【参考代码】
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105,MX=10000;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n,m,bx,by,K,now,las;
int a[N][N];
ll ans,f[2][N][N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void init()
{
now=0;las=1;
n=read();m=read();bx=read();by=read();K=read();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=read();
memset(f,0xc0,sizeof(f));f[now][bx][by]=0;
}
void walk()
{
now^=1;las^=1;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) for(int d=0;d<4;++d)
f[now][i][j]=max(f[now][i][j],f[las][i+dx[d]][j+dy[d]]+a[i][j]);
}
void solve()
{
if(K<=MX)
{
for(int i=1;i<=K;++i) walk();
printf("%lld\n",f[now][bx][by]);
}
else
{
for(int i=1;i<=MX/2;++i) walk();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) for(int d=0;d<4;++d)
ans=max(ans,f[now][i][j]*2-a[i][j]+(ll)(K-MX)/2*(a[i][j]+a[i+dx[d]][j+dy[d]]));
printf("%lld\n",ans);
}
}
int main()
{
freopen("2738.in","r",stdin);
freopen("2738.out","w",stdout);
init();solve();
return 0;
}
T2
【题目】
有一个
n
×
m
n\times m
n×m的网格,在
k
k
k秒内每秒会有一场灾难,用
(
x
,
y
,
r
)
(x,y,r)
(x,y,r)来表示,意为
∀
(
x
i
,
y
i
)
,
(
x
i
−
x
)
2
+
(
y
i
−
y
)
2
≤
r
2
\forall (x_i,y_i), (x_i-x)^2+(y_i-y)^2\leq r^2
∀(xi,yi),(xi−x)2+(yi−y)2≤r2,将
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi)上的数置零,否则加一。初始每个格子上的数字为
1
1
1,问
k
k
k秒后所有数之和。
n
,
m
≤
1
0
5
,
k
≤
100
n,m\leq 10^5,k\leq 100
n,m≤105,k≤100
【解题思路】
我们可以对每一条横线进行考虑,那么我们要考虑的就是一个线段与圆交的问题,这个可以通过简单计算完成。我们现在得到每条斜线上的若干个交点,用一个
set
\text{set}
set来维护计算这条斜线上最终所有数字的和即可。
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=105;
int lx,ly,n;
ll ans;
vector<pii>p;
set<int>st;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
ll sqr(ll x){return x*x;}
struct data
{
ll x,y,r;
void in(){x=read();y=read();r=read();r=sqr(r);}
}a[N];
ll calc()
{
ll res=0;int las=1;
sort(p.begin(),p.end());st.clear();st.insert(0);
//for(int i=0;i<p.size();++i) printf("%d %d\n",p[i].fi,p[i].se); puts("");
for(int i=0;i<(int)p.size() && las<=ly;++i)
{
if(p[i].fi>las) res+=(p[i].fi-las)*(*st.rbegin()),las=p[i].fi;
if(p[i].se>0) st.insert(p[i].se);
else st.erase(-p[i].se);
}
return res;
}
int main()
{
freopen("2741.in","r",stdin);
freopen("2741.out","w",stdout);
lx=read();ly=read();n=read();ans=(ll)lx*ly*n;
for(int i=1;i<=n;++i) a[i].in();
for(int i=1;i<=lx;++i)
{
p.clear();
for(int j=1;j<=n;++j) if(a[j].r>=sqr(i-a[j].x))
{
ll t=sqrt(a[j].r-sqr(i-a[j].x));
p.pb(mkp(a[j].y-t,j));p.pb(mkp(a[j].y+t+1,-j));
}
p.pb(mkp(ly+1,0));
ans-=calc();
}
printf("%lld\n",ans);
return 0;
}
T3
【题目】
有
n
n
n个人,每个人可以挑战人也可以被挑战,现在已知每个人是否挑战人和是否被挑战成功。挑战是有时间顺序的。当一个人被挑战成功后,接下来所有对他的挑战都会失败,同时他将不再会挑战别人。一个人最多挑战一次人,求满足要求的方案数。
n
≤
2500
n\leq 2500
n≤2500
【解题思路】
有
n
n
n个人,每个人可以挑战人也可以被挑战,现在已知每个人是否挑战人和是否被挑战成功。挑战是有时间顺序的。当一个人被挑战成功后,接下来所有对他的挑战都会失败,同时他将不再会挑战别人。一个人最多挑战一次人,求满足要求的方案数。
n
≤
2500
n\leq 2500
n≤2500
这道题初看十分神奇,因为需要满足的顺序限制很多,而且“挑战成功了还可以被继续挑战”这种限制更是难办,然后好像就做不了了。
但细细分析一下,我们可能可以得到一些可行的转化。
我们可以将这些挑战看作一个个序列,每一个序列均由
YN
\text{YN}
YN开头,后面跟着若干个
YY
\text{YY}
YY,最后由
NY
\text{NY}
NY或
YY
\text{YY}
YY或
YN
\text{YN}
YN结尾。
为什么可以这样考虑?其实需要抓住一个关键,结尾的人若是
NY
\text{NY}
NY,显然是可以的;若是
YY
\text{YY}
YY或
YN
\text{YN}
YN,那它不论挑战什么都是一样的,因为都失败了。
这里似乎没有考虑
NN
\text{NN}
NN,然而确实不用考虑。
所以我们的思路大概就出来了:给每个 YN \text{YN} YN序列分配若干个 YY \text{YY} YY,然后补上 NY \text{NY} NY,最后将多出来的 NY \text{NY} NY任意分配。
以下我们设 c n t 1 cnt_1 cnt1表示 YN \text{YN} YN的个数, c n t 3 cnt_3 cnt3表示 YY \text{YY} YY的个数。(为什么没有 c n t 2 cnt_2 cnt2?因为它是 NY \text{NY} NY的个数)
我们首先考虑知道了每个 YN \text{YN} YN序列分配了多少个 YY \text{YY} YY序列时我们怎么统计贡献?那么实际上就是一个经典的排列问题,设个数分别为 k i k_i ki个,答案就是 c n t 3 ! ∏ k i ! \frac{cnt_3!}{\prod k_i!} ∏ki!cnt3!
我们设
f
i
,
j
f_{i,j}
fi,j表示前
i
i
i个
YN
\text{YN}
YN,一共分配了
j
j
j个
YY
\text{YY}
YY的方案数。则:
f
i
,
j
=
∑
k
=
0
j
f
i
−
1
,
j
−
k
⋅
(
c
n
t
3
−
(
j
−
k
)
)
!
(
c
n
t
3
−
j
)
!
⋅
(
k
+
1
)
!
f_{i,j}=\sum_{k=0}^j f_{i-1,j-k}\cdot \frac{(cnt_3-(j-k))!} {(cnt_3-j)!\cdot (k+1)!}
fi,j=k=0∑jfi−1,j−k⋅(cnt3−j)!⋅(k+1)!(cnt3−(j−k))!
其中
(
c
n
t
3
−
(
j
−
k
)
)
!
(
c
n
t
3
−
j
)
!
=
P
c
n
t
3
−
(
j
−
k
)
k
\frac{(cnt_3-(j-k))!} {(cnt_3-j)!}=P^k_{cnt_3-(j-k)}
(cnt3−j)!(cnt3−(j−k))!=Pcnt3−(j−k)k,具体意义是我们分配了
k
k
k个
YY
\text{YY}
YY,然后还要除以一个顺序,
+
1
+1
+1是因为还有开头的
YN
\text{YN}
YN。
那么观察到阶乘项与
f
f
f的第二维长得十分像,我们进行移项,可以得到:
f
i
,
j
⋅
(
c
n
t
3
−
j
)
!
=
∑
k
=
0
j
f
i
−
1
,
j
−
k
⋅
(
c
n
t
3
−
(
j
−
k
)
)
!
(
k
+
1
)
!
f_{i,j}\cdot (cnt_3-j)!=\sum_{k=0}^j f_{i-1,j-k}\cdot \frac{(cnt_3-(j-k))!} {(k+1)!}
fi,j⋅(cnt3−j)!=k=0∑jfi−1,j−k⋅(k+1)!(cnt3−(j−k))!
这样令
g
i
,
j
=
f
i
,
j
⋅
(
c
n
t
3
−
j
)
!
g_{i,j}=f_{i,j}\cdot (cnt_3-j)!
gi,j=fi,j⋅(cnt3−j)!,
h
i
=
1
(
i
+
1
)
!
h_i=\frac 1 {(i+1)!}
hi=(i+1)!1,我们可以得到
g
i
,
j
=
∑
k
=
0
j
g
i
−
1
,
j
−
k
⋅
h
i
g_{i,j}=\sum_{k=0}^j g_{i-1,j-k} \cdot h_i
gi,j=k=0∑jgi−1,j−k⋅hi
这显然是个卷积的形式,于是:
G
(
x
)
×
H
(
x
)
=
G
′
(
x
)
G(x)\times H(x)=G'(x)
G(x)×H(x)=G′(x)
这里
G
(
x
)
G(x)
G(x)开始为
1
1
1,那么我们快速幂暴力卷积就可以做到
O
(
n
2
log
n
)
O(n^2\log n)
O(n2logn)了,当然如果你用
NTT
\text{NTT}
NTT优化一下就是
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n)了。
不过我们还能继续优化,而且写起来更简单。
观察到我们要求的实际上是
H
c
n
t
3
(
x
)
H^{cnt_3}(x)
Hcnt3(x),而这个形式十分优美:
H
(
x
)
=
∑
i
≥
0
x
i
(
i
+
1
)
!
H(x)=\sum_{i\geq 0} \frac {x^i} {(i+1)!}
H(x)=i≥0∑(i+1)!xi
约定
0
!
=
1
0!=1
0!=1根据泰勒展开
e
x
=
∑
i
≥
0
x
i
i
!
e^x=\sum_{i\geq 0} \frac {x^i} {i!}
ex=∑i≥0i!xi,那么我们实际上就是要将
e
x
e^x
ex去掉常数项再左移一项,于是:
H
(
x
)
=
e
x
−
1
x
H(x)=\frac {e^x-1} x
H(x)=xex−1
将它的
n
n
n次幂展开:
H
n
(
x
)
=
1
x
n
∑
i
=
0
n
(
−
1
)
n
−
i
e
i
x
(
n
i
)
H^n(x)=\frac 1 {x^n}\sum_{i=0}^n(-1)^{n-i}e^{ix}{n\choose i}
Hn(x)=xn1i=0∑n(−1)n−ieix(in)
由于右边有一个
1
x
\frac 1 x
x1,实际上每有一个相当于要求的幂次
+
1
+1
+1
于是我们将
1
x
\frac 1 x
x1去掉后,我们现在要求的是这个东西的
c
n
t
3
cnt_3
cnt3次方的系数
H
n
(
x
)
=
∑
i
=
0
n
(
−
1
)
n
−
i
e
i
x
(
n
i
)
H^n(x)=\sum_{i=0}^n(-1)^{n-i}e^{ix}{n\choose i}
Hn(x)=i=0∑n(−1)n−ieix(in)
于是我们再展开
e
i
x
e^{ix}
eix就可以得到答案了。
这样复杂度就是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的了(快速幂的
log
\log
log)
【参考代码1】( O ( n 2 log n ) O(n^2\log n) O(n2logn))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2505,mod=1e9+7;
int n,cnt[4];//1=YN,2=NY,3=YY
char s1[N],s2[N];
ll ans,fac[N],ifac[N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct poly
{
ll n,a[N<<1];
void clear(){for(int i=0;i<N<<1;++i)a[i]=0;n=0;}
poly operator *(const poly &x)const
{
poly res;memset(res.a,0,sizeof(res.a));res.n=n;
for(int i=0;i<n;++i) for(int j=0;j<n;++j) (res.a[i+j]+=x.a[i]*a[j])%=mod;
return res;
}
}h;
ll qpow(ll x,ll y)
{
if(y<0)return 0;ll res=1;
for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;
return res;
}
poly qpow(poly x,ll y)
{
poly res;res.clear();res.a[0]=1;
for(;y;y>>=1,x=x*x)if(y&1)res=x*res;
return res;
}
ll P(int x,int y){if(y<0 || y>x)return 0;return fac[x]*ifac[x-y]%mod;}
int cg(char c){return (c=='Y'?1:0);}
void init()
{
fac[0]=1;for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%mod;
for(int i=0;i<N;++i) ifac[i]=qpow(fac[i],mod-2);
}
int main()
{
freopen("2742.in","r",stdin);
freopen("2742.out","w",stdout);
int T=read();init();
while(T--)
{
cnt[1]=cnt[2]=cnt[3]=ans=0;
n=read();scanf("%s%s",s1,s2);
for(int i=0;i<n;++i) cnt[cg(s1[i])+cg(s2[i])*2]++;
//printf("cnt:%d %d %d\n",cnt[1],cnt[2],cnt[3]);
h.clear();h.n=cnt[3]+1;
//for(int i=0;i<h.n;++i) printf("%lld ",h.a[i]); puts("");
for(int i=0;i<=cnt[3];++i)h.a[i]=ifac[i+1];
//for(int i=0;i<h.n;++i) printf("%lld ",h.a[i]); puts("");
h=qpow(h,cnt[1]);
//for(int i=0;i<h.n;++i) printf("%lld ",h.a[i]); puts("");
ans=h.a[cnt[3]]*fac[cnt[3]]%mod*fac[cnt[1]+cnt[3]]%mod;
ans=ans*P(cnt[1],cnt[2])%mod*qpow(n-1,cnt[1]-cnt[2])%mod;
printf("%lld\n",ans);
}
return 0;
}
【参考代码2】( O ( n log n ) O(n\log n) O(nlogn))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2505,mod=1e9+7;
int n,cnt[4];//1=YN,2=NY,3=YY
char s1[N],s2[N];
ll ans,fac[N],ifac[N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
ll qpow(ll x,ll y)
{
if(y<0)return 0;ll res=1;
for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;
return res;
}
ll P(int x,int y){if(y<0 || y>x)return 0;return fac[x]*ifac[x-y]%mod;}
ll C(int x,int y){if(y<0 || y>x)return 0;return fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
int cg(char c){return (c=='Y'?1:0);}
void init()
{
fac[0]=1;for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%mod;
for(int i=0;i<N;++i) ifac[i]=qpow(fac[i],mod-2);
}
int main()
{
freopen("2742.in","r",stdin);
freopen("2742.out","w",stdout);
int T=read();init();
while(T--)
{
cnt[1]=cnt[2]=cnt[3]=ans=0;
n=read();scanf("%s%s",s1,s2);
for(int i=0;i<n;++i) cnt[cg(s1[i])+cg(s2[i])*2]++;
for(int i=0;i<=cnt[1];++i)
(ans+=(((cnt[1]-i)&1)?-1:1)*C(cnt[1],i)%mod*qpow(i,cnt[1]+cnt[3])%mod)%=mod;
ans=(ans+mod)%mod;
ans=ans*fac[cnt[3]]%mod*P(cnt[1],cnt[2])%mod*qpow(n-1,cnt[1]-cnt[2])%mod;
printf("%lld\n",ans);
}
return 0;
}
【总结】
妙啊!妙啊!