UOJ Logo cj_crq的博客

博客

P7655

2025-08-05 07:24:52 By cj_crq

看着楼上 dalao 的最短路,本蒟蒻直接不会了,本蒟蒻只会爆搜

题目简述

给定一个有向图,它的边有二特殊性质,及可在奇数天通过或在偶数天通过,以及一边只得在同一天通过。要求给出一条一天之中边权和不超过 $ 12 $ 且最短的路径。


分析

观察数据范围,我们发现 $ n \le 100 $,直接暴力搜索。


具体做法

对于每一条路径,记 $ tot $ 为总小时数 $ day $ 为总天数,$ time $ 为当天小时数。

那么,简单分析可得,对于一条路径上的一个点,只有两种选择:

  1. 枚举与他相邻的每一条边,当 $ time + \text{边权} \le 12 $ 且当天可过,则直接继续 dfs。

  2. 等待一天,继续 dfs。

存图时建议使用链式前向星,在枚举邻边时较快。

特别强调,要建 双向边

最后就是无脑 dfs 了。


Code

#include<bits/stdc++.h>
using namespace std;
int max(int a,int b){
    return a>b?a:b;
}
int min(int a,int b){
    return a<b?a:b;
}//自带min,max太慢了,小优化
int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-'){
            f=-1;
        }
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*f;
}//快读
void write(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9){
        write(x/10);
    }
    putchar(x%10+'0');
    return;
}//快写
int a,b,n,m,u,v,x,head[101],sum,i,cnt,ans=0x3f3f3f3f,tot,sda;//a为出发点,b为目标点,n为城市数,m为节点数,u为出发城市,v为到达城市,x为走过道路花费的时间,cnt为每一次dfs时经过的城市数,ans为最小花费小时数,tot为每一次dfs时花费的小时数,sda为最小花费时经过的城市数
bool vis[101];//标记城市是否被访问过
struct node{
    int to,nxt,num,flag;//链式前向星结构体,flag为奇偶标志
};
node e[2001];
void add(int a,int b,int c,int d){
    e[++sum].num=c;
    e[sum].to=b;
    e[sum].nxt=head[a];
    e[sum].flag=d;
    head[a]=sum;
}//链式前向星建图
struct dot{
    int fr,to,num,day;
};
dot cit[101],well[101];//按输出要求,fr为出发编号,to为到达编号,num为道路长度,day为日期
void dfs(int x,int day,int t,int skiped){//x为当前到达城市,day为日期,t为时间(当天的第几小时),skiped为停留次数
    int i;
    if(tot>ans){//一个小优化,如果当前花费时间大于当前最优解,直接return
        return ;
    }
    if(x==b){
        if(ans>tot){//更新最优解
            sda=cnt;
            ans=tot;
            for(i=1;i<=cnt;i++){
                well[i].day=cit[i].day,well[i].fr=cit[i].fr,well[i].to=cit[i].to,well[i].num=cit[i].num;
            }
        }
        return;
    }
    //当天不停留继续旅行
    for(i=head[x];i!=0;i=e[i].nxt){//遍历每一条相邻的边
        if(!vis[e[i].to]&&day%2==e[i].flag&&t+e[i].num<=12){//如果边连接的点没访问过&&当天可通行&&加上这条边后当天旅行时间<=12就访问该点
            tot+=e[i].num;//加上花费时间
            vis[e[i].to]=true;//标记访问过
            cit[++cnt].fr=x;//记录答案
            cit[cnt].to=e[i].to,cit[cnt].num=e[i].num,cit[cnt].day=day;
            dfs(e[i].to,day,t+e[i].num,0);//继续dfs
            cnt--;//回溯
            vis[e[i].to]=false;
            tot-=e[i].num;
        }
    }
    //停留一天
    tot+=24-t;//+24-t与当天时间t相加,得24小时,等于第二天0:00出发
    if(skiped<2){//若停留次数大于等于2,就不如不停
        dfs(x,day+1,0,skiped+1);//停留一天继续走
    }
    tot-=24-t;//回溯
    return ;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    a=read();
    b=read();
    n=read();
    m=read();
    for(i=1;i<=m;i++){
        u=read();
        v=read();
        x=read();
        add(u,v,x,1);//正向建一条u到v边权为x,奇数天可以通过的边
        add(v,u,x,0);//反向建一条偶数天可以通过的边
    }
    dfs(a,1,0,0);//无脑dfs
    for(i=1;i<=sda;i++){//输出
        write(well[i].fr);
        putchar(' ');
        write(well[i].to);
        putchar(' ');
        write(well[i].day);
        putchar(' ');
        write(well[i].num);
        putchar('\n');
    }
    return 0;
}

本蒟蒻第一篇题解,管理员大大求过!!!

最后祝大家NOIP2024 rp++!!!

题解:P1624 单词缩写

2025-08-05 07:24:20 By cj_crq

2024/10/16 upd:更改了 update 的写法。

description

题目讲得很清楚了,唯一的坑点是缩写中,不一定一个字母对应一个单词


solution

看一眼数据范围,$len \le 150$,考虑 dp


读入

先:

cin>>s[0];//读入第一行缩写

然后再 while 循环处理:

while(1){
    cin>>s[++sum];
    if(mp[s[sum]]){//去除无效字母
        sum--;
        continue;
    }
    if(sum==1&&s[0]=="LAST"&&s[1]=="CASE"){//结束条件
        break;
    }
    if(s[sum][0]>='A'&&s[sum][0]<='Z'){//开始处理的条件,即读到缩写就处理上一行
        sum--;//把下一行的缩写踢出去

以及:

s[0]=s[sum+1];//将下一行缩写存到s[0]
sum=0;

dp 实现

设 $dp_{i,j}$ 表示前 $i$ 个单词缩写,对应前 $j$ 个字母的方案数。那么,根据乘法原理和加法原理,可得转移为:

$$ dp_{i,j} = \sum_{k = i-1}^{j - 1} sum_{i,k,j-1} dp_{i-1,k} $$

记 $s$ 为缩写从第 $k$ 到第 $j-1$ 个字母的子串,则 $sum _{i,k,j-1}$ 表示 $s$ 在第 $i$ 个单词中出现的次数。而求 $sum$ 的过程刚好对应求 $s$ 与第 $i$ 个单词最长公共子序列的长度刚好为 $s$ 的长度的计数。而我们有 P2516 最长公共子序列

至于求和为什么是从 $i-1$ 到 $j-1$。因为一个单词至少对应一个缩写字母,前 $i-1$ 个单词要对应 $i-1$ 个字母,所以下界为 $i-1$。同理,第 $i$ 个单词至少要对应一个字母,所以上界为 $j-1$。

于是,欢愉地 AC 了


code

//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int max(int a,int b){
    return a>b?a:b;
}
int min(int a,int b){
    return a<b?a:b;
}
int read(){
    int x=0,lcs=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-'){
            lcs=-1;
        }
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*lcs;
}
void write(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9){
        write(x/10);
    }
    putchar(x%10+'0');
    return;
}
int n,sum,i,j,ans,k,dp[151][151],lcs[2][151],num[2][151];
char c;
string fei[101],s[152],ss;
pair<int,int> dpp(int k,int l,int rr){
    int i,j;
    string a=" ",b=" "+s[k];
    for(i=l;i<=rr;i++){
        a=a+s[0][i];
    }
    int n=a.size()-1,m=b.size()-1,now=1,pre=0;
    for(i=0;i<=m;i++){
        num[0][i]=1; 
        num[1][i]=0;
        lcs[0][i]=0;
        lcs[0][i]=0;
    }
    num[1][0]=1;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            lcs[now][j]=max(lcs[pre][j],lcs[now][j-1]);
            num[now][j]=0;
            if(a[i]+32==b[j]){
                lcs[now][j]=max(lcs[now][j],lcs[pre][j-1]+1); 
            }
            if(a[i]+32==b[j]&&lcs[now][j]==lcs[pre][j-1]+1){
                num[now][j]+=num[pre][j-1];
            }
            if(lcs[pre][j]==lcs[now][j]){
                num[now][j]+=num[pre][j];
            }
            if(lcs[now][j-1]==lcs[now][j]){
                num[now][j]+=num[now][j-1];
            }
            if(lcs[pre][j-1]==lcs[now][j]){
                num[now][j]-=num[pre][j-1];
            }
        }
        now=pre;pre=1-pre;
    }
    return {lcs[pre][m],num[pre][m]};
}
map<string,bool> mp;
int main(){
    //freopen("abbr.in","r",stdin);
    //freopen("abbr.out","w",stdout);
    n=read();
    for(i=1;i<=n;i++){
        cin>>fei[i];
        mp[fei[i]]=1;
    }
    if(n!=0){
        c=getchar();    
    }
    cin>>s[0];
    while(1){
        cin>>s[++sum];
        if(mp[s[sum]]){
            sum--;
            continue;
        }
        if(sum==1&&s[0]=="LAST"&&s[1]=="CASE"){
            break;
        }
        if(s[sum][0]>='A'&&s[sum][0]<='Z'){
            sum--;
            for(i=0;i<=sum;i++){
                for(j=0;j<=s[0].size();j++){
                    dp[i][j]=0;
                }
            }
            for(i=1;i<=s[0].size()-sum+1;i++){
                pair<int,int> tmp=dpp(1,0,i-1);
                if(tmp.first==i){
                    dp[1][i]=tmp.second;
                }
                else{
                    break;
                }
            }
            for(i=2;i<=sum;i++){
                for(j=i;j<=s[0].size()-sum+i;j++){
                    for(k=j-1;k>=i-1;k--){
                        pair<int,int> tmp=dpp(i,k,j-1);
                        if(tmp.first==j-k){
                            dp[i][j]+=dp[i-1][k]*tmp.second;
                        }
                        else{
                            break;
                        }
                    }
                }
            }
            cout<<s[0];
            if(dp[sum][s[0].size()]!=0){
                printf(" can be formaed in ");
                write(dp[sum][s[0].size()]);
                puts(" weys");
            }
            else{
                puts(" is not a velid abbraviation");
            }
            s[0]=s[sum+1];
            sum=0;
        }
    }
    return 0;
}

附上几个样例

【LGR-235-Div.2】洛谷 8 月月赛 I & WAOI R2, FeOI R1.5挂分祭

2025-08-05 07:23:37 By cj_crq

rt

恢复现役后的第一场比赛,打得真是依托答辩。


赛前

中午在机房打摆打题发现有月赛,遂报名。


赛中

T1

机房里第一个开题,看了眼第一题一眼很水,结果码力不行机房倒数第二切。

T2

一眼没啥思路,猜了几个结论后感觉像是每个数前面有比他小的就能删,遂写了个权值线段树维护(没想到 $O(n) $ 方法因为我菜),想到组合数学统计答案,结果写的和想的不一样,挂了样例,然后——就没有然后了,花了1.5h想了个“正解”(至少赛时我是这么认为的,后来发现伪了,只能骗特殊性质的分),拿了四十分,导致我认为是实现炸了,寄。

T3

T2调不出来遂开T3,感觉比T2简单(赛后发现确实是的)。这个输入一眼构造题(最不会构造题了)。赛时造了一个多小时没早出来,寄。

T4

看着机房里的dalao一个个都320了,遂弃疗开T4。没啥思路,写了个暴力骗分还挂了一个点,寄。


赛后

周末放假,疯狂肝作业,实在太累了改改比赛题换换脑子,结果随手造出T3,T2发现自己想到正解不写写歪解直接气疯了。感觉掉大分,寄。


总结

挂分310->175。

我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻我是蒟蒻

P13551 ももいろの鍵题解

2025-08-05 07:18:53 By cj_crq

闲话

很水的构造题,但是没有场切(太蒟蒻力)。


正文

solution

一次在 CF 上看到了 Constructive Algorithms 的 tag,于是在 oiwiki 上了解了一下。

很容易发现这题满足构造题的特点:输入规模大($n \in [1,1e5]$),答案存在某种规律(手推一下 $n \in [1,10]$ 的答案就可以发现)。

所以这是道构造题,然后呢?


特殊性质

可以观察下特殊性质,正解往往可以从特殊性质上推出,这题也不例外。

这题的特殊性质是 $\forall n , \exists k \ge 0 ,k \in \mathbb N ,n = 2^k -1$,翻译过来就是对于所有的 $n$ 存在自然数 $k$ 使得 $n = 2^k -1$。

有什么用呢?

首先,题面出现了按位与,考虑位运算。在二进制中,第 $i$ 位对应 $2^i$(设最低位为第 $0$ 位),正如十进制中第 $i$ 位对应 $10^i$。而 $2^k$ 正对应一个第 $k$ 位为 $1$,其余位为 $0$ 的二进制数。

那么 $2^k -1$ 呢?

由于二进制里是逢二进一,所以一个前 $k-1$ 位为 $1$ 的二进制数加 $1$ 后会怎么样呢?首先,第 $0$ 位加 $1$ 变 $2$,逢二进一,向第 $1$ 位进 $1$,自己变 $0$;然后第 $1$ 位加 $1$ 变 $2$,逢二进一,向第 $2$ 位进 $1$,自己变 $0$;此后同理,第 $k-1$ 位加 $1$ 变 $2$,逢二进一,向第 $k$ 位进 $1$,自己变 $0$,最终得到第 $k$ 位为 $1$,其余为 $0$ 的二进制数,即 $2^k$。所以反过来,$2^k-1$ 就是一个前 $k-1$ 位为 $1$ 的二进制数。

有什么启示呢?

不难发现,对于 $[1,2^k-1]$ 中的任何一个数 $num$,$2^k-1$ 减去 $num$ 与 $num$ 按位与一定为 $0$($1$ 相互错开),那么对于特殊性质,一种可行的构造就是:将前一半的数与 $n$ 减去它分在一组,因为 $[0,2^k-1]$ 中有 $2^k$ 个数,为 $2$ 的倍数,所以一定恰好分完。


正解

受特殊性质启发,我们可以先求出第一个大于等于 $n$ 的数 $Maxn$,满足 $\exists k \ge 0 ,k \in \mathbb N , Maxn = 2^k -1$。然后将 $[ Maxn - n,n]$ 中的数按上述方法分组(前一半的数与 $Maxn$ 减去它分一组),会发现剩下的 $[0,Maxn - n -1]$ 是完全相同的子问题,递归求解即可。


code

#pragma GCC target("sse,sse2,sse3,ssse3,sse4,avx,avx2,bmi,bmi2,fma")
#include<bits/stdc++.h>
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar()  (p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*f;
}
inline void write(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
int n,t;
void solve(int x){
    if(x==0){
        printf("1 0\n");
        return;
    }
    if(x<0){
        return;
    }
    int tmp=log2(x);
    int maxn=(1<<(tmp+1))-1;
    for(int i=maxn-x;i<=maxn/2;i++){
        printf("2 %d %d\n",i,maxn-i);
    }
    solve(maxn-x-1);
}
signed main(){
  //freopen(".in","r",stdin);
  //freopen(".out","w",stdout);
    t=read();
    while(t--){
        n=read();
        write(n/2+1);
        putchar('\n');
        solve(n);
    }
    return 0;
}

还是很水的喵~

cj_crq Avatar