一年前的某场教育场,一年后才开始补这套题,发现还是错的非常惨,我好菜啊
感觉这一场题目出得还是非常好的,题目都不是很难,没有搞你很偏的东西,主要是有很多实现的细节,帮助应该还是挺大的,链接:https://codeforces.com/contest/954
-
ABC 大水题就不说了 -
D. Fight Against Traffic - 题意:给你一张所有边权都为1的无向图,求出点对 ( x , y ) (x,y) (x,y)的数量,点对满足如下性质:
- x , y x,y x,y之间没有边
- 加入权值为1的边 ( x , y ) (x,y) (x,y), s s s到 t t t的最短路径长度不变
- 解法:分别从 s s s和 t t t跑一次最短路,就有了到所有点的最短路径,然后枚举点对 ( x , y ) (x,y) (x,y),可以发现,如果加入边 ( x , y ) (x,y) (x,y)后 s s s到 t t t的最短路长度减小,则新的最短路径必经过 ( x , y ) (x,y) (x,y),这样就可以求出新的最短路径长度,与未加入该边时的最短路径长度对比,如果更长,则 a n s + + ans++ ans++;
#include<bits/stdc++.h>
using namespace std;
int n,m,u,v,s,t;
bool mp[1005][1005];
int diss[1005],dist[1005];
vector<int> vec[1005];
void dijkstra(int *dis,int s)
{
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que;
que.push(make_pair(0,s));
for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f;
dis[s]=0;
while(!que.empty()){
pair<int,int> cur=que.top();que.pop();
if(dis[cur.second]<cur.first) continue;
for(int i=0;i<vec[cur.second].size();i++){
int nxtv=vec[cur.second][i];
if(dis[nxtv]>dis[cur.second]+1){
dis[nxtv]=dis[cur.second]+1;
que.push(make_pair(dis[nxtv],nxtv));
}
}
}
}
int main()
{
scanf("%d %d %d %d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d %d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
mp[u][v]=mp[v][u]=true;
}
dijkstra(diss,s);
dijkstra(dist,t);
int path=diss[t],ans=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int k=min(diss[i]+dist[j]+1,diss[j]+dist[i]+1);
if(!mp[i][j]&&k>=path) ans++;
}
}
printf("%d\n",ans);
}
-
E. Water Taps - 题意: n n n个能放温水的水龙头,温度 t i t_i ti,单位时间内最大水量为 x i x_i xi, n n n个水龙头的水混合后的温度为 ∑ i = 1 n x i ∗ t i ∑ i = 1 n x i \frac{\sum_{i=1}^{n}{x_i*t_i}}{\sum_{i=1}^{n}{x_i}} ∑i=1nxi∑i=1nxi∗ti给定最后的温度 T T T,求每分钟最多能放出多少水
- 解法:令上式等于
T
T
T,先将公式变形:
∑
i
=
1
n
(
t
i
−
T
)
∗
x
i
=
0
\sum_{i=1}^{n}{(t_i-T)*x_i}=0
i=1∑n(ti−T)∗xi=0
首先将所有水龙头调到最大,然后如果上式 > 0 >0 >0也就说明温度过高,那么贪心的调小最大温度的水龙头,这样才能保证更多的水,同样,如果上式 < 0 <0 <0也就说明温度不够,那么就从温度最低的开始调节
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int n,t;
struct node{
int a,b;
friend bool operator<(const node &oth1,const node & oth2){
return oth1.b<oth2.b;
}
}water[maxn];
int main()
{
ll tot=0;double ans=0;
scanf("%d %d",&n,&t);
for(int i=1;i<=n;i++) scanf("%d",&water[i].a);
for(int i=1;i<=n;i++){
scanf("%d",&water[i].b);
tot+=(ll)(water[i].b-t)*water[i].a;
}
sort(water+1,water+n+1);
if(tot>0){
for(int i=n;i>=1;i--){
if(water[i].b==t) {
ans+=water[i].a;
continue;
}
double cur=(double)tot/(water[i].b-t);
if(cur>=(double)water[i].a){
tot-=(ll)(water[i].b-t)*water[i].a;
}else{
tot=0;
ans+=(double)water[i].a-cur;
}
}
}else {
for(int i=1;i<=n;i++){
if(water[i].b==t) {
ans+=water[i].a;
continue;
}
double cur=(double)tot/(water[i].b-t);
if(cur>=(double)water[i].a){
tot-=(ll)(water[i].b-t)*water[i].a;
}else{
tot=0;
ans+=(double)water[i].a-cur;
}
}
}
printf("%.15lf\n",ans);
}
-
F. Runner’s Problem - 题意:给你一个 3 ∗ m 3*m 3∗m迷宫,求从 ( 2 , 1 ) (2,1) (2,1)走到 ( 2 , m ) (2,m) (2,m)的路线方案数,其中有 n n n段墙,第 i i i段在 a i a_i ai行,起点终点分别为 l i , r i l_i,r_i li,ri
- 解法:如果没有墙的话显然是一个简单的矩阵快速幂,其矩阵如下所示:
{ 1 1 0 1 1 1 0 1 1 } \left\{ \begin{matrix} 1 & 1 & 0 \\ 1 & 1 & 1 \\ 0 & 1 & 1 \\ \end{matrix} \right\} ⎩⎨⎧110111011⎭⎬⎫
那么如果某行某列有墙,只用把该矩阵对应该行的列的所有元素变为 0 0 0就可以了,举个栗子,假如第一二行都有墙,那么矩阵变为
{ 0 0 0 0 0 1 0 0 1 } \left\{ \begin{matrix} 0 & 0 & 0 \\ 0 & 0 & 1 \\ 0 & 0 & 1 \\ \end{matrix} \right\} ⎩⎨⎧000000011⎭⎬⎫
所以可以将所有的区间离散处理,然后分段矩阵快速幂就好了(也就是一段区间如果没一点三行的墙位置都不变,那么就可以矩阵快速幂) - 函数参数写成 i n t int int爆掉 d e b u g debug debug一下午还能说啥
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4;
const ll mod=1e9+7;
ll mat_[][3]={{1,1,0},{1,1,1},{0,1,1}};
template<typename T>
inline void read(T &x) {
x = 0;int f = 1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
x=x*f;
}
struct matrix{
ll mat[maxn][maxn];int siz;
matrix(int a=maxn){
siz=a-1;
memset(mat,0,sizeof(mat));
}
matrix operator*(const matrix &b){
matrix res;
for(int i=1;i<=siz;i++){
for(int k=1;k<=siz;k++){
if(mat[i][k]){
for(int j=1;j<=siz;j++){
if(b.mat[k][j]){
res.mat[i][j]=(res.mat[i][j]+(mat[i][k]*b.mat[k][j])%mod)%mod;
}
}
}
}
}
return res;
}
matrix pow(long long k){
matrix res,tmp=*this;
for(int i=1;i<=siz;i++) res.mat[i][i]=1;
while(k){
if(k&1) res=res*tmp;
tmp=tmp*tmp;
k>>=1;
}
return res;
}
void print(){
for(int i=1;i<=siz;i++){
for(int j=1;j<=siz;j++){
printf("%lld%c",mat[i][j],j==siz?'\n':' ');
}
}
}
}a,b,res;
struct node{
ll l,r;
node(ll a=0,ll b=0){
l=a;r=b;
}
friend bool operator<(const node &a,const node & b){
return a.l<b.l;
}
}seg[4][10005];
int row,tot[4],pre[4],n;
ll l,r,point[4][20005],m,block[10],nxt[10];
void count_(ll column)
{
memset(block,0,sizeof(block));
for(int i=1;i<=3;i++){
int pos=upper_bound(point[i]+1,point[i]+2*tot[i]+1,column)-point[i];
if(pos%2==0) block[i]=1;
}
}
matrix get()
{
matrix res;
for(int j=1;j<=3;j++){
for(int i=1;i<=3;i++){
res.mat[i][j]=block[j]?0:mat_[i-1][j-1];
}
}
return res;
}
int main()
{
read(n);read(m);
for(int i=1;i<=3;i++) res.mat[i][i]=1;
for(int i=1;i<=n;i++) {
read(row);read(l);read(r);
seg[row][++pre[row]]=node(l,r+1LL); //+1方便处理,很重要的细节
}
for(int i=1;i<=3;i++) sort(seg[i]+1,seg[i]+pre[i]+1);
for(int i=1;i<=3;i++){
for(int j=1;j<=pre[i];j++){
if(seg[i][j].l<=seg[i][tot[i]].r){
seg[i][tot[i]].r=max(seg[i][tot[i]].r,seg[i][j].r);
}else{
seg[i][++tot[i]]=seg[i][j];
}
}
}
for(int i=1;i<=3;i++){
for(int j=1;j<=tot[i];j++){
point[i][2*j-1]=seg[i][j].l;
point[i][2*j]=seg[i][j].r;
}
}
ll cur=2;
while(cur<=m){
ll pos=2e18;
for(int i=1;i<=3;i++) {
int nxt_pos=upper_bound(point[i]+1,point[i]+2*tot[i]+1,cur)-point[i];
if(nxt_pos==2*tot[i]+1) nxt[i]=m+1LL;
else nxt[i]=point[i][nxt_pos];
pos=min(pos,nxt[i]);
}
count_(cur);
matrix cur_mat=get();
res=res*cur_mat.pow(pos-cur);
cur=pos;
}
printf("%lld\n",res.mat[2][2]);
}
-
G. Castle Defense - 题意:一坐城墙分成 n n n段,初始每一段都有一定数量的小兵,位置为 i i i的小兵能攻击的范围是 [ m a x ( 1 , i − r ) , m i n ( n , i + r ) ] [max(1,i-r),min(n,i+r)] [max(1,i−r),min(n,i+r)],定义第 i i i段城墙的防御系数为所有能攻击该段城墙的小兵数量,现在给你 k k k只小兵,你要把他们分配进去,求 n n n段最小的防御系数最大
- 解法:贪心+二分
二分防御系数,check的时候每次贪心的把需要加入的小兵放到位置 i + r i+r i+r,因为这样对后面的帮助最大,然后维护一下区间和,注意这题的数据应该不能使用线段树,指针维护一下就行了 - 二分右边界开成1e18又wa了好几发
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int n,r;
ll a[maxn],k,init[maxn],b[maxn];
bool check(ll che)
{
ll pre=0,res=0;
memset(b,0,sizeof(b));
for(int i=1;i<=n;i++){
//pre+=b[i];
int nxt=min(n,i+r);
if(init[i]+pre<che){
b[nxt]+=che-(init[i]+pre);
res+=che-(init[i]+pre);
pre+=che-(init[i]+pre);
if(res>k) return false;
}
pre-=(i-r<=0?0:b[i-r]);
}
return res<=k;
}
int main()
{
scanf("%d %d %lld",&n,&r,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
ll pre=0;
for(int i=1;i<=n;i++){
pre-=i-r-1<=0?0:a[i-r-1];
init[i]+=pre;
pre+=a[i];
}
pre=0;
for(int i=n;i>=1;i--){
pre+=a[i];
pre-=i+r+1>n?0:a[i+r+1];
init[i]+=pre;
}
ll l=0,r=2e18,ans=0;
while(l<r){
ll mid=(l+r)>>1;
if(check(mid)) {
ans=mid;
l=mid+1;
}
else r=mid;
}
printf("%lld\n",ans);
}
-
H. Path Counting - 题意:给你一棵树,这棵树有如下特征:深度为 i i i的节点都拥有 a i a_i ai个儿子,求树上点对 ( x , y ) (x,y) (x,y)数量使得 x x x到 y y y的简单路径长度为 j j j(j=1,2…2*n-2),注意(1,2)和(2,1)只算一次
- 解法:简单树形dp问题,但是当然和普通的还是有区别的,你不能遍历每一个节点的呀,然而你发现每个相同深度的节点特征完全一样,所以直接遍历一条深度为 n n n的链就行了,然后注意滚动数组优化一下,复杂度 O ( n 2 ) O(n^2) O(n2)。
我第一发就MLE了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5005;
const ll mod=1e9+7;
int n;ll a[maxn],inv2;
ll dp[2][10005],son[5005][5005],mul[5005],ans[10005];
ll quick_pow(ll a,ll b)
{
ll res=1ll;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
void dfs(int cur)
{
if(cur==n+1) return;
dp[cur%2][0]=1;
for(int i=0;i<=2*n-2;i++){
dp[(cur+1)%2][i+2]=(i>n?0:son[cur+1][i])*(a[cur]-1)%mod;
if(i>0) dp[(cur+1)%2][i+1]=(dp[cur%2][i]+dp[(cur+1)%2][i+1])%mod;
else dp[(cur+1)%2][1]=1;
}
for(int i=1;i<=2*n-2;i++){
ans[i]=(ans[i]+(dp[cur%2][i]+(i>n?0:son[cur][i]))*mul[cur]%mod)%mod;
}
dfs(cur+1);
}
int main()
{
scanf("%d",&n);mul[1]=1;inv2=quick_pow(2,mod-2);
for(int i=1;i<n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) {
son[i][0]=1;
mul[i+1]=mul[i]*a[i]%mod;
for(int j=1;i+j<=n;j++) son[i][j]=son[i][j-1]*a[i+j-1]%mod;
}
dfs(1);
for(int i=1;i<=2*n-2;i++) printf("%lld%c",ans[i]*inv2%mod,i==2*n-2?'\n':' ');
}
-
H. Path Counting - 每次操作可以将两个等长字符串中所有的字符 a a a变成字符 b b b,定义两个等长字符串的距离为:使得两字符串相同的最小操作数,给定字符串 s s s和 t t t,求s中所有的长度为 t t t的子串与 t t t的距离
- 解法:并查集或
F
F
T
FFT
FFT
位置对应相同的两个字符之间连边,那么形成的图中两两之间可以转化,那么合并的次数就是两个字符串之间的距离,然而你会发现这个复杂度最高可以达到 4 ∗ 1 0 9 4*10^9 4∗109,但是每次只有一个简单的赋值操作,常数很小,再加上 C F CF CF评测机那么快,就能水过去了233
当然官方给出的解法是 F F T FFT FFT,这里也给出了代码
- 并查集
#pragma GCC optimize("O2")
#pragma GCC optimize("unroll-loops")
#include<bits/stdc++.h>
using namespace std;
const int maxn=125005;
char s[maxn],t[maxn];
int n,m,edge[10][10];
struct dsu{
int fa[10],rank[10];
void init(int k){
for(int i=1;i<=k;i++) fa[i]=i,rank[i]=0;
}
int fin(int k){
return fa[k]==k?k:(fa[k]=fin(fa[k]));
}
bool unite(int a,int b){
int x=fin(a),y=fin(b);
if(x==y) return false;
if(rank[x]<rank[y]) fa[x]=y;
else{
fa[y]=x;
if(rank[x]==rank[y]) rank[x]++;
}
return true;
}
bool same(int a,int b){
return fin(a)==fin(b);
}
}tree;
int main()
{
scanf("%s %s",s+1,t+1);
n=strlen(s+1);m=strlen(t+1);
for(int i=1;i+m-1<=n;i++){
tree.init(6);int ans=0;
memset(edge,0,sizeof(edge));
for(int j=i;j<=i+m-1;j++) edge[s[j]-'a'+1][t[j-i+1]-'a'+1]=1;
for(int i=1;i<=6;i++) for(int j=1;j<=6;j++) if(edge[i][j]&&tree.unite(i,j)) ans++;
printf("%d ",ans);
}
}
- F F T FFT FFT
#include <bits/stdc++.h>
using namespace std;
struct Complex{
double r, i;
Complex(double r = 0, double i = 0): r(r), i(i){}
inline Complex operator +(const Complex& rhs){
return Complex(r + rhs.r, i + rhs.i);
}
inline Complex operator -(const Complex& rhs){
return Complex(r - rhs.r, i - rhs.i);
}
inline Complex operator *(const Complex& rhs){
return Complex(r * rhs.r - i * rhs.i, i * rhs.r + r * rhs.i);
}
inline Complex operator /=(const int& rhs){
r /= rhs;
return *this;
}
};
const int N = 5e5;
const long double PI = acos(-1.0);
char s[N], t[N];
int p[10][N];
int sum[N], rev[N];
Complex v1[N], v2[N];
int find(int x, int id){
return x == p[x][id] ? x : p[x][id] = find(p[x][id], id);
}
bool same(int x, int y, int id){
return find(x, id) == find(y, id);
}
void unite(int x, int y, int id){
int fx = find(x, id), fy = find(y, id);
p[fy][id] = fx;
}
void Fast_Fourier_Transform(Complex *a, int n, int inverse){
for (int i = 0; i < n; i++){
if (rev[i] > i) swap(a[i], a[rev[i]]);
}
for (int h = 2; h <= n; h <<= 1){
double ang = inverse * 2 * PI / h;
Complex wn(cos(ang), sin(ang));
for (int j = 0; j < n; j += h){
Complex w(1, 0);
for (int k = j; k < j + h / 2; k++){
Complex u = a[k];
Complex t = w * a[k + h / 2];
a[k] = u + t;
a[k + h / 2] = u - t;
w = w * wn;
}
}
}
if (inverse == -1){
for (int i = 0; i < n; i++) a[i] /= n;
}
}
int main(){
int n, m, sz, ans;
scanf("%s", s);
n = strlen(s);
scanf("%s", t);
m = strlen(t);
sz = 2;
while (sz < n + m) sz <<= 1;
for (int i = 0, j = 0; i < sz; i++){
rev[i] = j;
int k = sz;
while (j & (k >>= 1)) j &= ~k;
j |= k;
}
for (int i = 0; i < n; i++){
for (int j = 0; j < 6; j++){
p[j][i] = j;
}
}
for (int i = 0; i < 6; i++){
for (int j = 0; j < 6; j++){
if (i == j) continue;
for (int k = 0; k < sz; k++) v1[k] = v2[k] = Complex(0, 0);
for (int k = 0; k < n; k++){
if (s[k] == 'a' + i) v1[k] = Complex(1, 0);
}
for (int k = 0; k < m; k++){
if (t[k] == 'a' + j) v2[m - k - 1] = Complex(1, 0);
}
Fast_Fourier_Transform(v1, sz, 1);
Fast_Fourier_Transform(v2, sz, 1);
for (int i = 0; i < sz; i++) v1[i] = v1[i] * v2[i];
Fast_Fourier_Transform(v1, sz, -1);
for (int k = 0; k < n; k++){
if (v1[k].r > 0.1){
if (!same(i, j, k)){
unite(i, j, k);
sum[k]++;
}
}
}
}
}
for (int i = m - 1; i < n; i++) printf("%d ", sum[i]);
printf("\n");
return 0;
}