Logo __vector__ 的博客

博客

标签
暂无

CF1649D 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-13 13:17:11

题目大意:

给定 $n$ 和 $c$。
给定一个长度为 $n$ 的数组 $a (1 \le a_i \le c$) 。有以下规则:
数组中拿出任意两个数 $x,y (y \le x)$,如果 $\lfloor \frac{x}{y} \rfloor$ 也存在于数组中,那么称这个数组是完美的。
问 $a$ 数组是不是完美的。
$1 \le n \le 10^6$
$1 \le c \le 10^6$

题目分析:

可以发现,题目限定了每个数组元素 $a_i \le c$,也就是说 $a_i$ 不会超过 $10^6$,可以用一种类似于线性筛的方法求解。
枚举 $i$ 从 $2$ 到 $n$,作为除数。里面再套一层枚举 $j$ 作为商 $(i \cdot j \le c)$。此时被除数的范围是 $(i \cdot j,i \cdot j + i-1)$。显然,如果数组中的某一个数在当前被除数范围中,并且数组中不存在商 $j$,那么数组 $a$ 就不是完美的。

那么如何判断某个数值范围内是否有数出现在 $a$ 中呢,可以使用前缀和的方法。定义一个数组 $hash$,记录 $a$ 中每一个值出现次数,代码如下:

for(int i=1;i<=n;i++)
{
    a[i]=read();
    hash[a[i]]++;
}

然后定义一个数组 $qzh$,$qzh_i$ 代表 $1$ 到 $i$ 的所有数字在 $a$ 中的出现次数。假设被除数数值范围是 $(l,r)$,如果 $qzh[r]-qzh_{l-1}$ 的值为 $0$,那么说明数组 $a$ 没有一个元素在被除数数值范围内,否则说明数组 $a$ 至少有一个元素在被除数数值范围内。

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=1e6+5;
namespace Main
{
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(!isdigit(ch))
        {
            if(ch=='-')f=-1;
            ch=getchar();
        }
        while(isdigit(ch))
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    int hash[maxn];
    int qzh[maxn];
    int a[maxn];
    bool vis[maxn];
    int t;
    int n,c;
    void main()
    {
        t=read();
        while(t--)
        {
            n=read();
            c=read();
            for(int i=1;i<=n;i++)
            {
                a[i]=read();
                hash[a[i]]++;
                vis[a[i]]=1;
            }
            for(int i=1;i<=c;i++)
            {
                qzh[i]=qzh[i-1]+hash[i];
            }\/\/方便确定某一个范围内的数的数量
            bool flag=1;
            for(int i=2;i<=c;i++)
            {
                if(!vis[i])continue;
                for(int j=1;i*j<=c;j++)
                {\/\/枚举因数,跟线性筛有些像
                    int l=i*j;
                    int r=i*j+i-1;
                    r=min(r,c);
                    if((qzh[r]-qzh[l-1])&&!vis[j])
                    {
                        flag=0;
                    }
                }
            }
            if(!flag)
            {
                printf("No\n");
            }
            else
            {
                printf("Yes\n");
            }
            for(int i=1;i<=n;i++)
            {
                hash[a[i]]--;
                vis[a[i]]=0;
            }
        }
    }
}
int main()
{
    Main::main();
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

How to AK ABC243

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-17 22:04:44

前言:

赛场上 C 题吃了 $23$ 个罚时,D题时间不够了(赛后 1 分钟想出正解)。
结果呢?
rating: $\color{brown} 500 \rightarrow 497 \color{black}(-3)$
rating 暴跌 3 px

A - Shampoo

暴力即可。

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
int main()
{
    int v,a,b,c;
    cin>>v>>a>>b>>c;
    int cnt=2;
    while(1)
    {
        cnt++;
        cnt%=3;
        if(cnt==0)
        {
            if(v<a)
            {
                printf("F");
                return 0;
            }
            v-=a;
        }
        if(cnt==1)
        {
            if(v<b)
            {
                printf("M");
                return 0;
            }
            v-=b;
        }
        if(cnt==2)
        {
            if(v<c)
            {
                printf("T");
                return 0;
            }
            v-=c;
        }
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

B - Hit and Blow

暴力即可。

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=1005;
int a[maxn],b[maxn];
int n;
int main()
{
    cin>>n;
    int ans1=0,ans2=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)if(a[i]==b[i])ans1++;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)continue;
            if(a[i]==b[j])ans2++;
        }
    }
    printf("%d\n%d",ans1,ans2);
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

C - Collision 2

题目大意:

给你 $n$ 个点的坐标 $(x_i,y_i)$。每个点都有一个运动方向(向左或向右),假如让每个点不断运动,是否会有两个点相遇?
保证初始时任意两个点坐标不同。

题目分析:

维护一个结构体 $node$,存储每个点的坐标和方向。然后根据横坐标对其从小到大进行排序。
定义一个 map 容器:

map<pair<int,char>,vector<int>> im; 

代表纵坐标为 pair.first,方向为 pair.second 的所有点的横坐标有哪些(由 vector 存储)
然后遍历每一个点,假设其纵坐标为 $y$ ,横坐标为 $x$ 如果其方向为向左,那么看一下存储 (( 当前纵坐标上所有向右的点 ) 的横坐标) 的 vector 的第一个元素,如果第一个元素的值小于 $x$,那么输出 Yes,否则则说明当前 vector 没有元素符合小于等于 $x$,那么跳过。(因为已经从小到大排序了)
如果当前方向为右,应该能举一反三吧我就懒得说了

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
map<pair<int,char>,vector<int>> im; 
const int maxn=2e5+5;
struct Node
{
    int x,y,s;
}node[maxn];
int n;
char s[maxn];
inline bool cmp(Node a,Node b)
{
    return a.x<b.x;
}
int main()
{
    int cnt=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&node[i].x,&node[i].y);
    }
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
    {
        node[i].s=s[i];
    }
    sort(node+1,node+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        im[make_pair(node[i].y,node[i].s)].emplace_back(node[i].x);
    }
    for(int i=1;i<=n;i++)
    {
        if(node[i].s=='L')
        {
            for(int pos:im[make_pair(node[i].y,'R')])
            {
                if(pos<=node[i].x)
                {
                    printf("Yes");
                    return 0;
                }
                else
                {
                    break;
                }
            }
        }
        if(node[i].s=='R')
        {
            int po=im[make_pair(node[i].y,'L')].size()-1;
            if(po<0)continue;
            if(im[make_pair(node[i].y,'L')][po]>=node[i].x)
            {
                printf("Yes");
                return 0;
            }
        }
    }
    printf("No");
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

D - Moves on Binary Tree

题目分析:

可以发现到达父节点就是 $x$ >>= $1$, 到达左子节点就是 $x$ <<= $1$,到达右子节点就是 $x$ = $x$ << $1$ | $1$。
把二进制弄成高精,string 存一下。最后 bitset 转换成 long long 就行了。

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=1e6+5;
char s[maxn];
typedef long long ll;
ll n,x;
string ans;
int main()
{
    scanf("%lld%lld",&n,&x);
    scanf("%s",s+1);
    bitset<100> bit(x);
    ans+=bit.to_string();
    for(int i=1;i<=n;i++)
    {
        if(s[i]=='U')
        {
            ans.pop_back();
        }
        if(s[i]=='L')
        {
            ans.append("0");
        }
        if(s[i]=='R')
        {
            ans.append("1");
        }
    }
    bitset<maxn> out(ans);
    printf("%lld",out.to_ulong());
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

E - Edge Deletion

题目分析:

floyd 求出每两个点之间最短路,然后枚举每一条边判断是否可以被其他边替代,如果当前边可以被替代那么 $ans$++,最后输出 $ans$

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=305;
const int maxm=45000;
int n,m;
int imap[maxn][maxn];
int a[maxm],b[maxm],c[maxm];
signed main()
{
    memset(imap,0x3f3f3f3f,sizeof(imap));
    scanf("%d%d",&n,&m);
    int ai,bi,ci;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&ai,&bi,&ci);
        a[i]=ai;
        b[i]=bi;
        c[i]=ci;
        imap[ai][bi]=ci;
        imap[bi][ai]=ci;
    }
    for(int mid=1;mid<=n;mid++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                imap[i][j]=min(imap[i][j],imap[i][mid]+imap[mid][j]);
            }
        }
    }
    int ans=0;
    bool flag;
    for(int i=1;i<=m;i++)
    {
        flag=0;
        for(int j=1;j<=n;j++)
        {
            if(imap[a[i]][j]+imap[j][b[i]]<=c[i])
            {
                flag=1;
            }
        }
        ans+=flag;
    }
    printf("%d",ans);
    return 0;
}

F - Lottery

题目前置:

有理数取余,应该谁都会吧(清北灵堂清北学堂好像讲过)

题目分析:

设 $p_i = \frac{W_i}{\Sigma^N_{j=1}W_j}$
显然,第 $i$ 个物品抽到了 $c_i$ 个的概率为 ${p_i}^{c_i}$。所有的物品综合起来,概率即为 $\prod_{i=1}^{n} {p_i}^{c_i}$
此处引用 @sunjiangnan55 的图片:

根据这个定理,假设抽了 $k$ 次。把 $k$ 次抽奖机会分配给 $n$ 个物品,每个物品抽到了 $c_i$ 个,那么排列方式就有 $\frac{k!}{\prod_{i=1}^{n} {c_i!}}$ 个。
两个概率乘起来就是(抽奖抽了 $k$ 次之后,第 $i$ 号物品获得 $c_i$ 个)的概率:$\prod_{i=1}^{n} {p_i}^{c_i} \cdot \frac{k!}{\prod_{i=1}^{n} {c_i!}}$
来通过 dp 求解。
设 $f[i][j][k]$ 表示 $i$ 个物品,抽了 $k$ 次后得到了 $j$ 个不同奖品的方案数。
状态转移: $f[i][j][k] = (f[i-1][j][k] + \Sigma_{l=1}^{k} f[i-1][j-1][k-l] \cdot {p_i}^{l} \cdot {l!}^{998244351}) mod 998244353$
这里我解释一下,${l!}^{998244351}$ 代表 $l!$ 的逆元;$l$ 的意思就是新抽了多少次奖;从 $j-1$ 转移过来是代表增加了一个种类的奖品,因为只增加了一个种类 ,但是抽中了 $l$ 个奖品,所以这些新抽中的一定是同一种类的(这个新种类就是当前第 $i$ 个物品),这种情况发生的概率为 ${p_i}^{l}$,所以要乘上 ${p_i}^{l}$。

AC 代码:

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=55;
const int mod=998244353;
int n,m,k;
int w[maxn];
ll p[maxn];\/\/每个点被选中的概率
ll sum;\/\/w总和
inline int ksm(ll a,ll b,ll mod)
{
    ll ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
inline int ny(ll a,ll mod)
{\/\/逆元
    return ksm(a,mod-2,mod);
}
ll fact[maxn];
ll f[maxn][maxn][maxn];
\/\/i个物品,抽了k次之后得到了j个不同奖品
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&w[i]);
        sum+=w[i];
    }
    for(int i=1;i<=n;i++)
    {\/\/预处理被选概率
        p[i]=((ll)w[i]*ny(sum,mod))%mod;
    }
    fact[0]=1;
    for(int i=1;i<maxn;i++)
    {
        fact[i]=fact[i-1]*i%mod;
    }
    f[0][0][0]=1;
    for(int i=1;i<=n;i++)
    {
        int tmp=min(i,m);
        for(int j=0;j<=tmp;j++)
        {
            for(int kkksc03=0;kkksc03<=k;kkksc03++)
            {
                f[i][j][kkksc03]=f[i-1][j][kkksc03];
                if(j>0)
                {
                    for(int l=1;l<=kkksc03;l++)
                    {
                        f[i][j][kkksc03]+=(f[i-1][j-1][kkksc03-l]*ksm(p[i],l,mod)%mod*ny(fact[l],mod)%mod);
                        f[i][j][kkksc03]%=mod;
                    }
                }        
            }
        }
    }
    printf("%lld",f[n][m][k]*fact[k]%mod);
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

G - Sqrt

题目分析:

很容易得到一个 $O(\sqrt N)$ 的垃圾做法。显然,$f[x] = \Sigma_{i=1}^{\sqrt N} f[i]$,$f[1]=1$
尝试对此改进,可以通过只枚举 $1 \rightarrow N^{\frac{1}{4}}$,那么显然,对于 $i$,$f[i]$ 的 $i$ 可以由 从$\sqrt N$ 到 $i^2$的所有数一步步按题意操作得来。
所以答案就是 $\Sigma_{i=1}^{n^{\frac{1}{4}}} (f[i] \cdot(\sqrt N - i^2 + 1))$

AC代码:

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=6e6+5;
int t;
ll x;
ll great_sqrt(ll sum)
{
    ll res=(ll)sqrt(sum-0.1);
    while(res*res<=sum)res++;
    return res-1;
}
ll dp[maxn];
int main()
{
    dp[1]=1;
    for(int i=2;i<=55000;i++)
    {
        int tmp=great_sqrt(i);
        for(int j=1;j<=tmp;j++)
        {
            dp[i]+=dp[j];
        }
    }
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&x);
        if(x<=55000)
        {
            printf("%lld\n",dp[x]);
            continue;
        }
        ll x2=great_sqrt(x);
        ll x3=great_sqrt(x2);
        ll ans=0;
        for(ll i=1;i<=x3;i++)
        {
            ans+=(x2-i*i+1)*dp[i];
        }
        printf("%lld\n",ans);
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

CF1654C题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-21 22:48:01

题目大意:

Alice 初始有一个蛋糕,之后将会进行 $n-1$ 次操作。她每次都会选一块蛋糕,设这块蛋糕的大小为 $w$,那么她就将这块蛋糕切成两块大小分别为 $\lfloor \frac{w}{2}\rfloor,\lceil \frac{w}{2} \rceil$ 的蛋糕。给定一个切好的蛋糕序列 $a$,$a_i$ 代表第 $i$ 个蛋糕的重量。问有没有一个初始的蛋糕,使得经过一定的操作可以得到这个蛋糕序列。

题目分析:

必须要知道的:$w = \lfloor \frac{w}{2}\rfloor + \lceil \frac{w}{2} \rceil$
显然,初始的那个蛋糕的重量一定是现在已有的蛋糕重量的总和,记为 $sum$。
可以使用 dfs 来求解。dfs 定义如下:

bool dfs(long long x)

它的参数的意思就是当前切到的蛋糕的大小,首先判断当前蛋糕大小 $x$ 是否出现在给定的蛋糕序列中,如果确实出现,那么返回 1 即可,代表没问题。
然后,判断 $x$ 是否小于等于 $1$,如果是,那么代表无法继续往下分,返回 0,代表不行。
下一步,就是分别 dfs 由当前蛋糕切成的两块蛋糕即可,也就是($sum2$ 是 $x$ 除以 $2$ 下取整):

return dfs(sum2+(x&1))&&dfs(sum2);

其实 dfs 的本质就是模拟切的过程,一旦某一个蛋糕不能往下切而且对应不上给定的切完的蛋糕序列,那么给定的序列一定是错的。

注意:

最后的返回部分,要先判断 dfs(sum2+(x&1)) 这个部分的返回值是否为 0,如果是,那么不用执行 dfs(sum2) 这一部分了,不然复杂度爆炸。
当然,写成这样:

return dfs(sum2+(x&1))&&dfs(sum2);

也是可以的,因为 && 运算符的特性,如果前面的返回 0 就不会执行后面的了。
此处感谢 @VinstaG173 大佬。

AC 代码:

#include <bits\/stdc++.h>\/\/
using namespace std;
#define int long long
const int maxn=2e5+5;
int a[maxn];
int t;
int n;
int sum;
map<int,int> im;
bool dfs(int x)
{
    if(im[x])
    {
        im[x]--;
        return 1;
    }
    if(x==1)return 0;
    int sum2=x>>1;
     return dfs(sum2+(x&1))&&dfs(sum2);
}
signed main()
{
    scanf("%lld",&t);
    while(t--)
    {
        sum=0;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            sum+=a[i];
            im[a[i]]++;
        }
        if(dfs(sum))printf("YES\n");
        else printf("NO\n");
        for(int i=1;i<=n;i++)
        {
            im[a[i]]=0;
        }
    }
    return 0;
}

NOI online2022 提高组游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-26 13:10:50

比赛开始,先开 T1 ,一看是要维护一个单调栈。我就想了仿照单调队列的做法直接 $O(n)$ 维护。但是,看了一下,居然有 $q$ 组询问,人傻了。分析了大约半个小时,想不出优化,就暂时放弃,看后面的题。
我决定直接开 T3。我看了一下题目给出的函数值的图,发现了一个规律,就是对于图中对角线的部分,$f(x,y) = f(x-1,y) + f(x,y-1) - f(x-1,y-1)$。
但是,这个规律并不能看出来有什么用,就打了一个 $O(n^2)$ 的暴力走人。
回头来看 T2,题意是给出每个人会做的题,求是否存在有两个人有共同会做的题并且一个人会做的题不完全包含另一个人会做的题。我直接想到了一个思路,就是把每个人会做的所有题的编号状压到一个 int 变量里面。大概就是 problem[i] |= xi($x$ 是当前人会做的题目)。测了一下样例,发现假了,例如 1,3 这样的数据就能 hack。于是临时换成 bitset来记录。最后这道题最低 0 分,最高 40 分。
回去看 T1,快速打了一个 $O(nq)$ 的暴力走人。

预计最高得分:10+40+10=60 分。
预计最低得分:0+0+0=爆零
等官方数据吧。

UPD:

冥间数据 15+30+10=55分。

UPD:

官方数据 15+30+10=55分。

NOI online2022 入门组游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-26 19:13:38

中午闲的没事,就按照赛时的思路把提高组T2的随机化写了出来,交上去...........居然比我原来代码得分高了30分,后悔死了,早知道赛时就写随机化了。

先开T1,送分题啊,于是快速写完看下一题(我这么菜怎么可能AC)。看T2,让求一个数 $y$,使得 $x \cdot y \cdot gcd(x,y) = z$,并且 $y$ 必须是所有符合条件的数中最小的。开始推式子,推了半个小时,还是推不出来,心里顿时有一些慌。我就干脆先写一个 $O(log z \cdot t\sqrt z)$ 的暴力。忽然,发现一个规律,我成功优化成了 $O(t \sqrt z)$。继续观察,发现只需要枚举到 $\sqrt{\frac{z}{x}}$ 就行了。于是成功优化到了 $O(t\sqrt{\frac{z}{x}})$ ,测试大样例,没过。于是就到处查错,怎么都查不出错。查了半个小时还没看出来那有错,心态快要炸裂了。我就干脆用我的思路对其中一个使我 WA 掉的数据进行模拟,是对的。再看一眼代码,好家伙我的 gcd 函数没开 long long,修好之后成功过掉了大样例3。大样例4却TLE了。尝试优化,失败了。我怀着试一试的心态到 Linux 系统上测试了一下,居然跑得飞去,过了,而且只用了 34ms,比本地竟快了上千倍。
开T3,不会,放弃!就这么结束了。

最高估分:100+100+0=200
最低估分:0+0+0=0

UPD:

冥间数据:100+10+0=110
凉了
T2随便改了改变成了40分,ccf的大样例怎么这么水

UPD:

官方数据出来了,100+10+0=110,凉了。
另外发现如果赛场上考虑了没考虑到的情况,那么T2能从10分升到70分,这样就可以进入前 25%
希望下一次能上200

每日一个爆零小技巧

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-28 22:03:35

前言:

本 blog 收集我遇到的所有 bug。

csp-j当天

Dev-C++ 5.1.0 环境,如果在 CSP 考场用,不要开 -std=c++11

2022.3.28

1.平衡树为了求前驱后继临时建的节点记得删除
2.平衡树删数的时候要注意像这样 splay 之后:

int prenode = pre(val);
int nxtnode = nxt(val);
splay(prenode, 0);
splay(nxtnode, prenode);

权值为 val 的节点现在是它原来的后继 nxtnode 的左子节点,也就是 node[node[nxtnode].ch[0]]
3.平衡树每加入一个节点就要把它 splay 到根。

2022.4.17

如果要先读入一些东西,然后用 fgets 读入字符串的话,最好在 fgets 之前先 getchar 读取换行符,不然会炸。

int n;
char s[maxn];
cin>>n;
getchar();
fgets(s,maxn,stdin);

2022.6.8

使用主席树求解区间第 $k$ 小的时候有个地方要注意。就是如果查询的是右区间,那么这个 $k$ 要减去左区间的大小。也就是:

query(rs[l_node],rs[r_node],mid+1,r,k-count);

2022.6.10

  1. unique 只能对排好序的数组使用
  2. 不要不小心把离散化后的区间长度错当成原来的区间长度

2022.6.11

dfs 的时候,如果要跳过某个点,不要写成 return!不要写成 return!不要写成 return!

错误示范:

void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int to=edge[i].to;
		if(to==fa)return;
		dfs2(to,u);
		cf[u]+=cf[to];
	}
}  

正确:

void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int to=edge[i].to;
		if(to==fa)continue;
		dfs2(to,u);
		cf[u]+=cf[to];
	}
}  

2022.6.12

lca 的同时向上跳的部分,要注意的是要比较祖先而不是祖先的深度
错误示范:

for(int i=17;i>=0;i--)
{
	if(dep[fa[i][a]]!=dep[fa[i][b]])
	{
		a=fa[i][a];
		b=fa[i][b];
	}
}

正确:

for(int i=17;i>=0;i--)
{
	if(fa[i][a]!=fa[i][b])
	{
		a=fa[i][a];
		b=fa[i][b];
	}
}

2022.6.13

记得局部变量要初始化(windows系统局部变量不初始化问题不大,Linux 一定会炸)

2022.6.15

第一个

线段树一定不要把左子树和右子树写混!!!
错误示范:

val[i<<1|1]=lazy[i]*get_len(i<<1);  

正确:

val[i<<1|1]=lazy[i]*get_len(i<<1|1);

第二个

另外,对于一些要将区间所有数更改 $val$ 这样的题目,打 lazy tag 的时候之前要将 lazy 数组初始化为 $val$ 取值范围以外的数,标记下放判断是否有标记的部分也要作相应的修改。
下面是一个例子:
$0 \le val \le 10^9$
此时就不能将 lazy 数组初始化为 $0$ 了,现在将其初始化为 $-1$
然后标记下放部分就变成这样:

inline void push_down(int i)
{
	if(lazy[i]==-1)return;
	lazy[i<<1]=lazy[i];
	lazy[i<<1|1]=lazy[i];\/\/标记更新
	val[i<<1]=lazy[i]*get_len(i<<1);\/\/权值数组更新
	val[i<<1|1]=lazy[i]*get_len(i<<1|1);
	lazy[i]=-1;
}

第三个

还有一个关于 C++ 基础语法的一个
定义了一个指针之后,如果它没有指向某一个变量,而且想要用它存数据,得用 new 来给它分配空间
例如:

struct Node
{
	int cnt;
};
Node* tmp;
void main()
{
	tmp= new Node();
	tmp->cnt=114514;
	cout<<tmp->cnt<<endl;
}

第四个

使用 new 分配内存的变量,不会自动初始化,需要手动添加初始化函数。

2022.7.5

写树剖的时候,记得要先 dfs 预处之后再 build 线段树

2022.7.8

线段树 Build 函数,在递归完左右子树后记得 push_up 更新非叶子节点

2022.7.10

注意:这只适用于 EK 算法

在 BFS 图的时候,要注意每找到一个当前节点能到达的节点 $to$,就要标记上 $vis[to] = 1$,而不是从队列里取出元素时标记。如果不这么做,会导致一些点已经访问过了,但是在将它们取出队列之前没有标记上,如果一些点与它们有边相连而且比它们先从队列里取出,那么会造成重复访问,导致 TLE
正确:

inline bool bfs()
{
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(s);
    vis[s]=1;
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        for(int i=head[u];i;i=edge[i].nxt)
        {
            int to=edge[i].to;
            if(vis[to])continue;
            vis[to]=1;
            que.push(to);
        }
    }
    return 0;
}

错误:

inline bool bfs()
{
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(s);
    vis[s]=1;
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].nxt)
        {
            int to=edge[i].to;
            if(vis[to])continue;
            que.push(to);
        }
    }
    return 0;
}

2022.7.11

注意 unordered_map 可以被卡到 $O(n)$
使用的时候最好使用自定义哈希函数,这篇博客会进行相关讲解

2022.7.17

不要算错空间,特别是 OI 赛制下

2022.7.27

如果想要让 priority_queue 实现小根堆,应该这样写(假设要用 T 类型):
priority_queue<T,vector<T>,greater<T>> name

2022.8.12

局部变量记得初始化。

2022.8.14

哈希函数的模数不能只有一个,最好使用双哈希 + 不常用模数。

2022.8.16

欧拉回路相关问题,记得加当前弧优化。

2022.8.22

setmap 常数小得多。

2022.8.27

记得计算一下最大值可能是多少,例如如果数据最大可能是 $10^{12}$,而其中一部要计算平方,那么最好开 __int128

How to AK NOI online2022-senior

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-04-02 10:23:02

前言:

15+30+10=55pts,没进入前 25%,我是垃圾pxpxpx

T1-Stack

题目解法:

对于每一个成功的二元组,把它称为关键点(套用官方题解原话)。
然后观察一下可以发现,对于每一个二元组,都有一个区间 $[l,r]$,使其在这个区间中是关键点。可以对于每一个二元组,把最小的,满足其在区间 $[l,r]$ 中是关键点的 $l$ 求出来,记为 $p_i$。而它的 $r$ 是多少都可行,就记为 $n$。这样最后询问时,答案就是询问区间 $[l,r]$ 中满足 $\forall{i} \in [l,r],p_i \le l$ 的数量。
显然,$p$ 数组可以 $O(n)$ 预处理。
然后,从 $1$ 到 $n$ 枚举端点,依次将 $p_i$ 加入树状数组。如果当前点是某一组询问的左端点,当前点为 $i$,记当前小于等于 $i$ 的点的数量为 $sum(i)$,那么这组询问的答案减去 $sum(i)$,因为最后要求的是 $[l,r]$ 这个区间,所以先把 $sum(i)$(也就是区间 $[1,l-1]$ 的答案)减去。如果当前点是某一组询问的右端点,记这组询问的左端点为 $left$,当前小于等于 $left$ 的点的数量为 $sum(left)$,这组询问的答案加上 $sum(left)$(就是区间 $[1,r]$ 的答案),因为区间 $[1,l-1]$ 的答案已经提前减去了,所以现在的答案就是区间 $[l,r]$ 的答案。

注意:

  1. 一定要枚举到第 $i$ 个顶点时才把 $p_i$ 加入树状数组,不然会导致错误的统计了 $i+1$ 往后的顶点的答案。
  2. 一定要先删除多余的答案之后再把 $p_i$ 加入树状数组,不然会错误的减去 $p_i$对答案的贡献。

$\color{green}\text{AC 代码:}$

#include <bits\/stdc++.h>
using namespace std;
namespace Main
{
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(!isdigit(ch))
        {
            if(ch=='-')f=-1;
            ch=getchar();
        }
        while(isdigit(ch))
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    inline void write(int x)
    {
        if(x<0)putchar('-');
        if(x>=10)
            write(x\/10);
        putchar(x%10^48);
    }
    const int maxn=5e5+5;
    int n,q;
    int a[maxn];
    int b[maxn];
    int p[maxn];
    int sta[maxn];
    int top;
    int tre[maxn<<2];
    vector<int> left[maxn];
    vector<int> right[maxn];
    int given_l[maxn];\/\/每组询问的l
    int ans[maxn];
    inline int lowbit(int x)
    {
        return x&-x;
    }
    inline void add(int x,int val)
    {
        while(x<=n)
        {
            tre[x]+=val;
            x+=lowbit(x);
        }
    }
    inline int sum(int x)
    {
        int res=0;
        while(x>0)
        {
            res+=tre[x];
            x-=lowbit(x);
        }
        return res;
    }
    int main()
    {
        n=read();
        q=read();
        for(int i=1;i<=n;i++)
        {
            a[i]=read();
        }
        for(int i=1;i<=n;i++)
        {
            b[i]=read();
        }
        for(int i=1;i<=n;i++)
        {
            p[i]=1;
        }
        for(int i=n;i>=1;i--)
        {
            while((b[i]>b[sta[top]]&&(a[i]!=a[sta[top]]))&&top)
            {
                p[sta[top]]=i+1;
                top--;
            }
            sta[++top]=i;
        }
        int li,ri;
        for(int i=1;i<=q;i++)
        {
            li=read();
            ri=read();
            left[li].emplace_back(i);
            right[ri].emplace_back(i);
            given_l[i]=li;
        }
        for(int i=1;i<=n;i++)
        {\/\/枚举端点
            for(int j=0;j<left[i].size();j++)
            {
                ans[left[i][j]]-=sum(i);
            }
            add(p[i],1);
            for(int j=0;j<right[i].size();j++)
            {
                ans[right[i][j]]+=sum(given_l[right[i][j]]);
            }
        }
        for(int i=1;i<=q;i++)
        {
            write(ans[i]);
            putchar('\n');
        }
        #ifndef ONLINE_JUDGE
        system("pause");
        #endif
        return 0;
    }
}
int main()
{
    Main::main();
    return 0;
}

T2 -Discuss

咕咕咕

T3 -Sort

咕咕咕

P1119 灾后重建 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-15 22:34:35

$\color{green}\text{AC算法:}$ floyd

题目描述

有 $N$ 个村庄,$M$条公路,编号为 $0 \rightarrow N-1$。每个村庄都有一个修复完成时间 $ti$,为 $0$ 则不需要修复。
有 $q$ 个询问,每次询问在第 $t$ 天,$x \rightarrow y$ 的最短路。保证每次询问 $t$ 是不下降的。

题目分析:

观察一下数据范围,$n \le 200$ ,这明显是在暗示使用 floyd这种 $O(n^{3})$ 暴力来求最短路。同时如此小的数据范围可以用邻接矩阵。
可以用一个结构体 $czok$ 存储每个村庄的修复时间。用另一个结构体 $node$ 存储每组询问。可以将 $node$ 数组从按照 $t$ 的大小从小到大排序,为什么这么做之后会解释。

解题过程

读入完成后,就是刚才说的将 $node$ 数组从小到大排序。然后处理每一组询问。
对于每一组询问,可以依次遍历 $n$ 个点求最短路。但是这样复杂度为 $O(QN^{3})$,恭喜你TLE了。
想一想优化,这时候就可以利用每组询问的 $t$ 不下降的性质了。可以定义一个变量 $pos = 0$ ,遍历每组询问时从pos开始遍历,如果当前点 $pos$已经被修复,那么从 $pos$ 跑一遍floyd,然后 pos++ ,知道 $pos$ 没有被修复。可以发现,因为每组询问的 $t$ (第 $t$ 天)是不下降的,所以在之前询问中已经修复的点在当前询问中也一定已经修复,不用重新从已经跑过floyd的点中重新再跑一遍,这样每个点最多运行一次floyd,时间复杂度降为 $O(q + n^{3})$,可以AC。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f = -1;
        ch = getchar();
    }
    while(ch>='0'&&ch<='9'){
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
int n,m;
int dis[205][205];\/\/距离,邻接矩阵
bool vis[205];\/\/判断是否已经有了答案
int q;
struct Node{
    int u, t, time;
    int bh;
    int ans;
}node[50005];
struct Czok{
    int bh;\/\/它的编号
    int time;\/\/修复时间
}czok[205];
bool cmp(Node a,Node b){
    return a.time < b.time;
}
bool cmpbh(Node a,Node b){
    return a.bh < b.bh;
}
inline void floyd(int mid){
    for (int i = 1; i <= n;i++){
        for (int j = 1; j <= n;j++){
            dis[i][j]=min(dis[i][j],dis[i][mid]+dis[mid][j]);
        }
    }
}
int main(){
    memset(dis, 0x3f, sizeof(dis));
    n = read();
    m = read();
    for (int i = 1; i <= n;i++){
        dis[i][i] = 0;
    }
    for (int i = 1; i <= n; i++){
        czok[i].time = read();
        czok[i].bh = i;
    }
    for (int i = 1; i <= m; i++)
    {
        int u, t, v;
        u = read();
        t = read();
        v = read();
        u++;\/\/因为题目中起点是由0开始的,这里使用下标1,所以下标++
        t++;
        dis[u][t] = min(dis[u][t],v);\/\/重边保留最小
        dis[t][u] = min(dis[t][u], v);
    }
    q = read();
    for (int i = 1; i <= q;i++){
        int u, t, time;
        u = read();
        t = read();
        time = read();
        u++;\/\/原理同上
        t++;
        node[i].bh=i;
        node[i].u = u;
        node[i].t = t;
        node[i].time = time;
    }
    sort(node + 1, node + q + 1, cmp);
    int pos = 1;\/\/遍历所有村庄,遇到修复的就跑一边floyd
    for (int i = 1; i <= q;i++)
    {
        while(czok[pos].time<=node[i].time&&pos<=n){
            \/\/如果已经修复完成,并且没有越界,跑一边floyd
            int bh=czok[pos].bh;
            floyd(bh);
            vis[bh] = 1;
            pos++;
        }
        if(!vis[node[i].u]||!vis[node[i].t]||dis[node[i].u][node[i].t]>100000000)
            node[i].ans = -1;
        else
            node[i].ans = dis[node[i].u][node[i].t];
    }
    sort(node + 1, node + q + 1, cmpbh);
    for (int i = 1; i <= q;i++){
        cout << node[i].ans << endl;
    }
    return 0;
}

P5663 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-17 20:20:20

ZGC AK IOI!

$\color{green}\text{AC算法:}$ dfs

这道题可以发现,其实就是在求 $1$ 到要生产零件的工人 $a$的最短路,这个最短路的意思就是经过多少轮之后轮到轩轩($1$ 号)提供零件。显然,如果最短路 $\le l$,那么肯定会让轩轩提供原材料。
但是这样随便画个图就hack掉了,最后分析这个图,可以发现要开两个数组,分别存最短路为奇数和最短路为偶数的。分开之后即可通过。

放上我几个月前的代码:

#define lmnjuruo "这题的难度对于lmn蒟蒻来说为IOI\/IOI+"
#define zgcshenxian "这题的难度对于zgc神仙来说为入门-"
#include <iostream>\/\/cin,cout
#include <cstring>\/\/string类
#include <cstdio>\/\/scanf,printf
#include <cstdlib>\/\/freopen,system等
#include <string>\/\/string类
#include <algorithm>\/\/算法库
#include <cmath>\/\/数学库
#include <iomanip>\/\/输出控制
#include <vector>\/\/容器系列
#include <set>\/\/不常用
#include <map>\/\/hash映射
#include <deque>\/\/双向容器
#include <queue>\/\/优先队列
#include <bitset>\/\/二进制
#include <ctime>
#define PI 3.14\/\/圆周率
#define inf 0x3f3f3f3f\/\/无穷大
#define ll long long\/\/不开long long见祖宗
#define ull unsigned long long
#define reg register\/\/寄存器存放
#define O2 ios::sync_with_stdio(false)\/\/加速cin
#define ZGC_AKIOI "100%"
#define LHX_AKIOI "100%"
#define DY_AKIOI "100%"
#define DCY_AKIOI "100%"
#define ZGC "邹神过河拆黑题,顺手AKIOI"
#define lmn "lmn蒟蒻过河被红题挡,顺手爆零模拟赛"
#define lmntrl "lmn蒟蒻爆零CSP-J2021"
using namespace std;
inline int read()\/\/快读
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')
		{
			f=-1;
		}
		ch=getchar();
	}
	while(isdigit(ch))
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
inline void write(int x)
{
    char F[200];
    int tmp=x>0?x:-x ;
    if(x<0)putchar('-') ;
    int cnt=0 ;
       while(tmp>0)
       {
           F[cnt++]=tmp%10+'0';
           tmp\/=10;
       }
       while(cnt>0)putchar(F[--cnt]) ;
}
const int maxn=1e5+5;
int n,m,q;
vector<int> edge[maxn];
int disou[maxn],disji[maxn];
void bfs()
{
	memset(disou,0x3f3f3f3f,sizeof(disou));
	memset(disji,0x3f3f3f3f,sizeof(disji));
	queue< pair<int,int> > que;
	for(int i=0;i<edge[1].size();i++)
	{
		int to=edge[1][i];
		disji[to]=1;
		que.push(make_pair(1,to));
	}
	while(!que.empty())
	{
		int u=que.front().second;
		int dis=que.front().first;
		que.pop();
		for(int i=0;i<edge[u].size();i++)
		{
			int to=edge[u][i];
			if(dis&1)\/\/更新答案后当前为偶数 
			{
				if(disou[to]>dis+1)
				{
					disou[to]=dis+1;
					que.push(make_pair(dis+1,to));
				}
			}
			else
			{
				if(disji[to]>dis+1)
				{
					disji[to]=dis+1;
					que.push(make_pair(dis+1,to));
				}
			}
		}
	}
}
int main()
{
	O2;
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	bfs();
	for(int i=1;i<=q;i++)
	{
		int a,l;
		cin>>a>>l;
		if(l&1)
		{
			if(disji[a]<=l)puts("Yes");
			else puts("No");
		}else
		{
			if(disou[a]<=l)puts("Yes");
			else puts("No");
		}
	} 
	return 0;
}

降幂定理记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-03 11:54:23

$a^n\%p=a^{n\%(p-1)}\%p.$

当$p$是素数时成立。

共 320 篇博客