2020EC-final

本文探讨了两个算法问题:一是关于一个矩阵中矩形数量随着单元格逐渐消失的变化;二是寻找特定形式的字符串子序列。对于矩阵问题,提出了两种不同复杂度的解决方案,包括使用单调栈优化的算法。对于字符串子序列问题,通过分解成两部分并应用容斥原理求解。这些题目展示了在处理复杂度限制下的优化策略和动态规划思想。
摘要由CSDN通过智能技术生成

传送门

B - Rectangle Flip 2

题意:

给你一个 n ∗ m n*m nm的矩阵,接下来 n ∗ m n*m nm秒每秒都会消失一个格子,问每个时刻矩阵中构成的矩形有多少个。

n , m ≤ 500 n,m\le 500 n,m500

思路:

先说一下复杂度 n 4 n^4 n4但是跑不满的算法,对于每个删去的点,枚举左边以及右边每个位置,维护其能到的上下界,比如上界 x x x下界 y y y,当前点是 ( d x , d y ) (dx,dy) (dx,dy),那么答案就是 ( d x − y + 1 ) ∗ ( x − d x + 1 ) (dx-y+1)*(x-dx+1) (dxy+1)(xdx+1),看似 n 4 n^4 n4,实际 500 m s 500ms 500ms就跑完了。

还有一个稳定 n 3 n^3 n3用单调栈跑的并没有看懂代码是怎么写的,就先咕咕了。

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid (tr[u].l+tr[u].r>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;

//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;

const int N=510,mod=1e9+7,INF=0x3f3f3f3f;
const double eps=1e-6;

int n,m;
int D[N][N],U[N][N];
int st[N][N];

int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++){
            D[i][j]=n+1;
            U[i][j]=0;
        }
    }
    LL ans=1ll*n*(n+1)*m*(m+1)/4;
    for(int i=1;i<=n*m;i++) {
        int x,y; scanf("%d%d",&x,&y);
        for(int l=y,u1=0,d1=n+1;l>=1&&!st[x][l];l--) {
            u1=max(u1,U[x][l]+1);
            d1=min(d1,D[x][l]-1);
            for(int r=y,u2=u1,d2=d1;r<=m&&!st[x][r];r++) {
                u2=max(u2,U[x][r]+1);
                d2=min(d2,D[x][r]-1);
                ans-=1ll*(x-u2+1)*(d2-x+1);
            }
        }
        for(int j=x;j<=n;j++) U[j][y]=max(U[j][y],x);
        for(int j=x;j>=1;j--) D[j][y]=min(D[j][y],x);
        printf("%lld\n",ans);
        st[x][y]=1;
    }


















	return 0;
}
/*

*/









A - Namomo Subsequence

题意:

给你一个串 s s s,问你有多少个长度为 6 6 6且形如 A B C D C D ABCDCD ABCDCD的子序列。

6 ≤ ∣ s ∣ ≤ 1 e 6 6\le |s|\le 1e6 6s1e6,字符集大小 62 62 62

思路:

考虑到字符集和长度都很长,所以考虑 62 n 62n 62n的算法, 62 ∗ 62 n 62*62n 6262n的肯定过不去了。

直接维护肯定是不好弄的,可以发现能维护出来前两个位置就不错了,所以我们考虑将其切割,分开来看。

将其分成 A B AB AB C D C D CDCD CDCD两个部分来看,枚举 C C C的位置,那么答案就是两边的方案数乘起来。

对于 C D C D CDCD CDCD,我们比较容易维护出来,定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]代表类型为 i i i C = j , D = k C=j,D=k C=j,D=k的个数,其中 i = 2 i=2 i=2代表 i j i j ijij ijij i = 1 i=1 i=1代表 j i j jij jij i = 0 i=0 i=0代表 i j ij ij,那么转移也比较容易,直接从上一个状态切过来即可,注意每次 f [ 2 ] [ j ] [ k ] f[2][j][k] f[2][j][k]都要清零。

由于很难将左边的信息也记下来,记下来也需要 62 ∗ 62 62*62 6262来遍历,所以很不划算。

考虑容斥,将左边的所有方案(不包含相同字符个数相乘)直接于右边相乘,考虑这样多算了什么,显然多算了 A = C , A = D , B = D , B = C A=C,A=D,B=D,B=C A=C,A=D,B=D,B=C的情况,这个可以容斥来求。

复杂度 O ( 62 n ) O(62n) O(62n)

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid (tr[u].l+tr[u].r>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;

//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;

const int N=1000010,mod=998244353,INF=0x3f3f3f3f;
const double eps=1e-6;

int n;
int a[N];
LL pre[N][64];
LL f[3][64][64],n2;
char s[N];

int get(char c) {
    if(c>='a'&&c<='z') return c-'a'+1;
    else if(c>='A'&&c<='Z') return c-'A'+27;
    else return c-'0'+53;
}

void add(LL &x,LL y) {
    x+=y; 
    if(x>=mod) x-=mod;
}

void del(LL &x,LL y) {
    x-=y; 
    if(x<0) x+=mod;
}

LL qmi(LL a,LL b) {
    LL ans=1;
    while(b) {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans%mod;
}

int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);

    n2=qmi(2,mod-2);
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;i++) a[i]=get(s[i]);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=62;j++) {
            pre[i][j]=pre[i-1][j];
        }
        pre[i][a[i]]++;
    }
    LL ans=0;
    for(int i=n;i>=1;i--) {
        LL sum=0,all=i-1,i2=0;
        for(int j=1;j<=62;j++) add(i2,1ll*pre[i-1][j]*(all-pre[i-1][j])%mod);
        (i2*=n2)%=mod;
        for(int j=1;j<=62;j++) {
            if(a[i]==j) continue;
            add(f[2][a[i]][j],f[1][a[i]][j]);
            add(f[0][a[i]][j],1ll*pre[n][j]-pre[i][j]);
            add(f[1][j][a[i]],f[0][j][a[i]]);
            LL mul=((i2-pre[i-1][j]*(all-pre[i-1][j])%mod)%mod-pre[i-1][a[i]]*(all-pre[i-1][a[i]])%mod)%mod+pre[i-1][j]*pre[i-1][a[i]]%mod;
            mul%=mod; mul+=mod; mul%=mod;
            add(ans,f[2][a[i]][j]*mul%mod);
            f[2][a[i]][j]=0;
        }
    }
    printf("%lld\n",ans);

















	return 0;
}
/*

*/









D - City Brain

题意:

给你一个 n n n个点 m m m条边的无向图,边权初始为 1 1 1,你可以对边进行 k k k此操作,每次选择任意边,假设某条边操作了 x x x次,那么他的权值就变成了 1 x \frac{1}{x} x1,给你两对起点终点,问对边操作完之后,从两对起点到相应终点的路径和最短是多长。

n , m ≤ 5000 , 0 ≤ k ≤ 1 e 9 n,m\le 5000,0\le k\le 1e9 n,m5000,0k1e9

思路:

考虑如果两个路径有重叠,那么肯定是与在两条路各个路径上走不同的,所以考虑能否将其拿出来,单独考虑。

考虑如果两条路径有两部分是重叠的,那么肯定不优于两个人都从第一个重叠开始走同一路径到下一个重叠优,这样重叠就变成一段连续的区间,所以结论就是两条路径如果有重叠,那么一定是连续的一段。

考虑枚举重叠部分的长度,假设为 l l l,那么预处理一下重叠部分为 l l l的时候其他路径的最短和 l e n [ l ] len[l] len[l],这个 n 2 n^2 n2可以处理出来,我们发现我们不好确认分给重叠部分的次数多少,但是这时一个凹函数,所以我们三分给重叠部分的操作次数,让后 c h e c k check check一下,问题又变成了给长度为 l e n len len的路径分配 x x x次操作的最小代价,这个还是比较容易考虑的,首先就是均分次数,多余的次数也是均分,算一下就好,注意不要除 0 0 0

还有细节就是 n 2 n^2 n2处理 l e n len len的时候,枚举的公共路径的端点 i , j i,j i,j的时候,要分 s 1 − > i , j − > t 1 s1->i,j->t1 s1>i,j>t1还有 s 1 − > j , i − > t 1 s1->j,i->t1 s1>j,i>t1两种情况,对于 s 2 , t 2 s2,t2 s2,t2同理。

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid (tr[u].l+tr[u].r>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;

//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;

const int N=5010,mod=998244353,INF=0x3f3f3f3f;
const double eps=1e-6;

int n,m,k;
int s1,s2,t1,t2;
int dis[N][N];
vector<int>v[N];
int len[N];

//计算路径长度为x,能分配p个的时候最小值
double get(int p,int x) {
    if(x==0) return 0;
    int cnt=p/x,rest=p%x;
    return 1.0/(cnt+1)*(x-rest)+1.0/(cnt+2)*rest;
}

//返回分配给共同路径mid个,非共同路径k-mid个的答案
double check(int mid,int x) {
    int c1=k-mid,c2=mid;
    return get(c1,len[x])+get(c2,x)*2;
}

int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);

    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++) {
        int a,b; scanf("%d%d",&a,&b);
        v[a].pb(b); v[b].pb(a);
    }
    scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
    for(int i=1;i<=n;i++) {
        queue<int>q; q.push(i);
        for(int j=1;j<=n;j++) dis[i][j]=INF;
        dis[i][i]=0;
        while(q.size()) {
            int u=q.front(); q.pop();
            for(auto x:v[u]) {
                if(dis[i][x]>dis[i][u]+1) {
                    dis[i][x]=dis[i][u]+1;
                    q.push(x);
                }
            }
        }
    }
    memset(len,0x3f,sizeof(len));
    len[0]=dis[s1][t1]+dis[s2][t2];
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            int l=dis[i][j];
            if(l==INF) continue;
            if(!(dis[s1][i]==INF||dis[j][t1]==INF||dis[s2][i]==INF||dis[j][t2]==INF))
                len[l]=min(len[l],dis[s1][i]+dis[j][t1]+dis[s2][i]+dis[j][t2]);
            if(!(dis[s1][j]==INF||dis[i][t1]==INF||dis[s2][i]==INF||dis[j][t2]==INF))
                len[l]=min(len[l],dis[s1][j]+dis[i][t1]+dis[s2][i]+dis[j][t2]);
            if(!(dis[s1][i]==INF||dis[j][t1]==INF||dis[s2][j]==INF||dis[i][t2]==INF))
                len[l]=min(len[l],dis[s1][i]+dis[j][t1]+dis[s2][j]+dis[i][t2]);
            if(!(dis[s1][j]==INF||dis[i][t1]==INF||dis[s2][j]==INF||dis[i][t2]==INF))
                len[l]=min(len[l],dis[s1][j]+dis[i][t1]+dis[s2][j]+dis[i][t2]);
        }
    }
    double ans=1e13;
    for(int x=0;x<=n;x++) {
        if(len[x]==INF) continue;
        int l=0,r=k;
        while(l<=r) {
            int mid1=l+(r-l)/3,mid2=r-(r-l)/3;
            if(check(mid1,x)<check(mid2,x)) r=mid2-1;
            else l=mid1+1;
        }
        ans=min(ans,check(r,x));
    }
    printf("%.13f\n",ans);

	return 0;
}
/*
10 9 19
5 2
4 1
2 1
6 5
10 8
8 1
9 3
7 6
1 3
7 7 6 4
*/





  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值