A.倒水

B.二分图染色(弱化版)

C.合并回文子串

D.身体训练

E.数列互质

F.最长树链

A.倒水

有一个大水缸,里面水的温度为T单位,体积为C升。另有n杯水(假设每个杯子的容量是无限的),每杯水的温度为t[i]单位,体积为c[i]升。

现在要把大水缸的水倒入n杯水中,使得n杯水的温度相同,请问这可能吗?并求出可行的最高温度,保留4位小数。

注意:一杯温度为$t_1$单位、体积为$c_1$升的水与另一杯温度为$t_2$单位、体积为$c_2$升的水混合后,温度变为$(t_1 \times c_1+t_2 \times c_2)/(c_1+c_2)$,体积变为$c_1+c_2$。

二分

二分最终温度。

注意确定好$l$和$r$ 的范围,可能出现的除零操作。

复杂度$O(nlog_2T)$ 。

代码如下:

#include <cstdio>
#define N 100005
using namespace std;
typedef long long ll;
int n,T,C,t[N],c[N];
double Max(double a,double b){
    return a>b?a:b;
}
double Min(double a,double b){
    return a<b?a:b;
}
double Abs(double x){
    return x>0?x:-x;
}
int check(double x){
    double tot=0,temp;
    for(register int i=0;i<n;++i){
        if(Abs(*(t+i)-x)<1e-8)continue;
        if(Abs(T-x)<1e-8)return 0;
        if(x<T&&x<*(t+i))return 0;
        if(x>T&&x>*(t+i))return 0;
        temp=*(c+i)*(x-*(t+i))/(T-x);
        tot+=temp;
    }
    return tot<=C;
}
int main(void){
    scanf("%d%d%d",&n,&T,&C);
    scanf("%d%d",t,c);
    double l=Min(T,*(t+0)),r=Max(T,*(t+0));
    for(register int i=1;i<n;++i){
        scanf("%d%d",t+i,c+i);
        r=Min(r,Max(T,*(t+i)));
        l=Max(l,Min(T,*(t+i)));
    }
    double mid=(l+r)/2;
    while(r-l>1e-5){
        if(check(mid))l=mid;
        else r=mid;
        mid=(l+r)/2;
    }
    if(check(r)||check(l))printf("Possible\n%.4lf\n",r);
    else printf("Impossible\n");
}

B.二分图染色(弱化版)

待更

C.合并回文子串

输入两个字符串A和B,合并成一个串C,属于A和B的字符在C中顺序保持不变。如”abc”和”xyz”可以被组合成”axbycz”或”abxcyz”等。定义字符串的价值为其最长回文子串的长度(回文串表示从正反两边看完全一致的字符串,如”aba”和”xyyx”)。输出这个最大价值。

DP

定义状态

因为合并的字符串是连续的,故可以定义状态$dp[i][j][k][h]$ 表示字符串A从第$i$ 位开始连续$j$ 个字符与字符串B从第$k$ 位开始连续$h$ 个字符能否合并成一个回文串C。

转移方程

$dp[i][j][k][h] ;|= dp[i+1][j-2][k][h] ,a[i]=a[i+j-1]$ 且$j \geqslant 2$ ;

$dp[i][j][k][h] ;|= dp[i][j][k+1][h-2] ,b[k]=b[k+h-1]$ 且$ h\geqslant 2$ ;

$dp[i][j][k][h] ;|= dp[i][j-1][k+1][h-1] ,a[i+j-1]=b[k]$ 且$j \geqslant 1,h \geqslant 1$ ;

$dp[i][j][k][h] ;|= dp[i+1][j-1][k][h-1] ,a[i]=b[k+h-1]$ 且$j \geqslant 1,h \geqslant 1$ ;

边界情况

  • 当$j+h \leqslant 1$ 即合并串C的长度小于等于$1$ 时,该串必为回文串。
  • 考虑到边界$+1$ 后会超过数组范围,故$i \leqslant strlen(a),k \leqslant strlen(b)$ 。

复杂度$O(Tn^4)$ 。

代码如下:

#include <cstdio>
#include <cstring>
using namespace std;
int T,dp[55][55][55][55],lan,lbn,ans;
char a[55],b[55];
int Max(int a,int b){
    return a>b?a:b;
}
int main(void){
    scanf("%d",&T);
    while(T--){
        scanf("%s%s",a,b);
        lan=strlen(a);
        lbn=strlen(b);
        ans=0;
        memset(dp,0,sizeof(dp));
        for(int j=0;j<=lan;++j){
            for(int h=0;h<=lbn;++h){
                for(int i=0;i+j<=lan;++i){
                    for(int k=0;k+h<=lbn;++k){
                        if((j+h<=1)
                           ||(i<lan&&j>=2&&a[i]==a[i+j-1]&&dp[i+1][j-2][k][h])
                           ||(k<lbn&&h>=2&&b[k]==b[k+h-1]&&dp[i][j][k+1][h-2])
                           ||(i<lan&&k<lbn&&j>=1&&h>=1&&a[i]==b[k+h-1]&&dp[i+1][j-1][k][h-1])
                           ||(i<lan&&k<lbn&&j>=1&&h>=1&&a[i+j-1]==b[k]&&dp[i][j-1][k+1][h-1])){
                            dp[i][j][k][h]=1;
                            ans=Max(ans,j+h);
                        }
                    }
                }
            }
        }
        printf("%d\n",ans);
    }
}

D.身体训练

n个人排成一列跑步,前后两人之间相隔 u 米,每个人正常速度均为 v 米/秒。

最后一个人需要以当时自己的最高速度往前跑,直到超过排头的人 u 米,然后降回到原始速度 v米/秒。每个人最初的最高速度为c[i] 米/秒,每轮衰减d[i]米/秒,也就是说,如果i是第j个跑的,那么他的速度就是c[i]-(j-1)*d[i] 米/秒。n个人初始以随机的顺序排列,每种顺序的概率完全相等,跑完一轮(每个人都追到排头一次,序列恢复原样)的期望需要的时间是多少?

模拟

按题意直接暴力模拟$ans = \frac{1}{n}\sum_{i=1}^n\sum_{j=1}^n\frac{nu}{c[i]-(j-1)d[i]-v}$ 。

代码如下:

#include <cstdio>
using namespace std;
int n;
double ans,v,u,c[1005],d[1005];
int main(void){
    scanf("%d%lf%lf",&n,&v,&u);
    for(register int i=1;i<=n;++i)scanf("%lf",c+i);
    for(register int i=1;i<=n;++i)scanf("%lf",d+i);
    for(register int i=1;i<=n;++i)
        for(register int j=1;j<=n;++j)
            ans+=u*n/(c[i]-(j-1)*d[i]-v);
    printf("%.3lf\n",ans/n);
}

E.数列互质

给出一个长度为 n 的数列 { a[1] , a[2] , a[3] , … , a[n] },以及 m 组询问 ( l[i] , r[i], k[i])。求数列下标区间在 [ l[i] , r[i] ] 中有多少数在该区间中的出现次数与 k[i] 互质(最大公约数为1)。

莫队

考虑到查询可以离线,采用莫队算法。

用数组$cnt[x]$ 维护当前区间$[l,r]$ 中,数$x$ 的出现次数。

用$num[x]$ 维护当前区间$[l,r]$ 中,出现次数为$x$ 次的有$num[x]$ 个。设$num[x]$ 的长度为$len$ ,考虑极端情况$\frac{(1+len)len}{2}=n$ ,故$len < \sqrt{n}$ ,可选择map。

查询时,只需遍历整个$num[x]$ ,求得$ans=\sum_{gcd(x,k)=1}num[x]$ ,查询复杂度为$O(\sqrt{n} log_2^2n)$ 。

总复杂度为$O(n^{\frac{3}{2}}log_2^2n)$ 。

代码如下:

#include <cstdio>
#include <map>
#include <algorithm>
#define N 50005
#define D 250
using namespace std;
int n,m,a[N],cnt[N],ans[N];
map<int,int>num;
map<int,int>::iterator it;
struct node{
    int l,r,k,id;
    friend bool operator<(node a,node b){
        if(a.l/D==b.l/D)return a.r<b.r;
        return a.l/D<b.l/D;
    }
}q[N];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
void add(int x){
    int temp=cnt[a[x]];
    if(num.count(temp)){
        num[temp]--;
        if(num[temp]==0)
            num.erase(temp);
    }
    temp=++cnt[a[x]];
    num[temp]++;
}
void dec(int x){
    int temp=cnt[a[x]];
    num[temp]--;
    if(num[temp]==0)
        num.erase(temp);
    temp=--cnt[a[x]];
    if(temp)num[temp]++;
}
int query(int x){
    int temp=0;
    for(it=num.begin();it!=num.end();++it)
        if(gcd(it->first,x)==1)temp+=it->second;
    return temp;
}
int main(void){
    scanf("%d%d",&n,&m);
    for(register int i=0;i<n;++i)scanf("%d",a+i);
    for(register int i=0;i<m;++i){
        scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
        q[i].l--;
        q[i].r--;
        q[i].id=i;
    }
    sort(q,q+m);
    int l=0,r=-1;
    while(r<q[0].r){++r;add(r);}
    while(l<q[0].l){dec(l);++l;}
    ans[q[0].id]=query(q[0].k);
    for(int i=1;i<m;++i){
        while(r<q[i].r){++r;add(r);}
        while(r>q[i].r){dec(r);--r;}
        while(l<q[i].l){dec(l);++l;}
        while(l>q[i].l){--l;add(l);}
        ans[q[i].id]=query(q[i].k);
    }
    for(register int i=0;i<m;++i)
        printf("%d\n",ans[i]);
}

F.最长树链

待更