NOI2018 屠龙勇士 做题心得

今天A掉了这个题,感觉这是一个帮助我增强调试技巧的好题!!1

题解

首先分析,我们发现:对于每条龙,我们用什么伤害的剑,其实是确定的,与 \(x\) 无关,可以用简单的模拟求出这个东西。

然后相当于是这样一个方程:

\[\begin{cases} a_1 x \equiv b_1 \pmod{m_1}\\ a_2 x \equiv b_2 \pmod{m_2}\\ ...\\ a_n x \equiv b_n \pmod{m_n}\\ \end{cases} \]

对于每一条方程 \(ax\equiv b\pmod{m}\),我们把它变形。由于我们会excrt,我们知道,肯定是变成 \(x\equiv b\pmod{m}\) 好做。

考虑 \(g=\gcd(a,m)\)。如果 \(b\) 不是 \(g\) 的倍数,啪的一下,无解。这是显然的。否则,我们可以把 \(a,b,m\) 同事约去一个 \(g\),然后 \(a,m\) 就互质力!!!

互质,那好做,变成 \(x\equiv b\times a^{-1} \pmod{m}\)

然后就做一下 excrt 就行了

小结

首先那个观察非常重要,不过相对容易些

然后就是对式子的分析。就和学校里whk老师讲的做题方法一样,首先你手上要有一套方法,然后你要想,这个题目的问题,怎么转化成你手上的这些东西。

就好比我们会excrt,会做的其实是 \(x\equiv b\pmod{m}\)。而这个题目中和这个形式不同,因此我们要做转化,把它变成会做的这个形式。

关于调试

这题的数据卡的很死,因此尤其要小心爆long long的问题。

有一个常识是,exgcd是不会爆的。一开始是hyh和我讲的这个事情,我后来去u群确认了一下。

证:EI说对,那就是对!

其它地方会不会爆呢?这我们不好直接靠脑子想。先把代码写出来,过小样例再说。如果小样例你感觉太水,可以手造一些。手造数据的能力很重要,后面的对拍都需要靠手造样例。

关于造数据:
您可以先生成一个 \(x\),再随机一些 \(a\)\(m\),计算 \(ax\bmod m\),得到 \(b\)

有一件事情是,我们需要保证所有 \(m\)\(lcm\) 不超过 \(1e12\)

这并不困难,我们手造一个 \(1e12\) 左右,因数比较多的数。然后每次的 \(m\) 就随机取它一个因数就行

这里又有一个小技巧:随机取因数,不需要先求出因数,我们在造这个数的时候,就先把它分解,然后每个质数随机一个指数就行了。

对于小数据,可以把这个数取的小一些,比如 \(1e8\)

然后我们可以用 Linux 下的 -fsanitize=undefined 功能,找到哪些地方爆了long long。

然后多调几次就行了。

对于最后三个点,可能比较毒瘤。这里暴露的问题也许很难在调试中发现,如果实在不行就下数据。我个人感觉,靠手造数据调试,是可以做到85分的。

代码

本题

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N   200005
    #define int long long
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Tra(i,u) for(int i=G.h[u],v=G.to(i);~i;i=G.nx(i),v=G.to(i)) if (i>=0)
    #define MEM(a,x) memset(a,x,sizeof(a))
    #define FK(a) MEM(a,0)
    #define sz(x) ((int)x.size())
    #define all(x) x.begin(),x.end()
    #define p_b push_back
    #define pii pair<int,int>
    #define fir first
    #define sec second
    int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
    template <typename T> void Rd(T& arg){arg=I();}
    template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
    void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    int n,m;
    int a[N],p[N],g[N],h[N];
    void Input(int id=0)
    {
        Rd(n,m);
        F(i,1,n) a[i]=I();
        F(i,1,n) p[i]=I();
        F(i,1,n) g[i]=I();
        F(i,1,m) h[i]=I();
    }

    int smul(int a,int b,int m) // 龟速乘, 您可以用 __int128 代替(雾)
	{
		a=(a%m+m)%m; b=(b%m+m)%m;
		int r=0;
		while(b)
		{
			if (b&1) r=(r+a)%m;
			a=(a<<1)%m; b>>=1;
		}
		return r;
	}
	int gcd(int a,int b){if (a<0) a=-a; if (b<0) b=-b; while(b)swap(a,b),b%=a; return a;}
    int lcm(int a,int b){return a/gcd(a,b)*b;} // lcm,gcd: 不会爆
    void exgcd(int a,int b,int&x,int&y) // ax+by=gcd(a,b), 这个也不会爆
    {
        if (b==0) {x=1; y=0; return;}
        exgcd(b,a%b,y,x); y-=x*(a/b);
    }
    int inv(int a,int b){int x,y; exgcd(a,b,x,y); return (x%b+b)%b;}
    bool sol(int a,int b,int&x,int&y,int c,int mod) // ax+by=c, 返回是否有解
    {
		b=-b; // b一定是负的
        int g=gcd(a,b); 
		if (c%g) {x=-1; y=-1; return false;}
        a/=g; b/=g; c/=g;
        exgcd(a,b,x,y); // ax+by=1
        x=smul(x,c,mod); y=smul(y,mod-c,mod);  // 小心!
		return true;
    }
	namespace exCRT
    {
        bool no_sol=0;
        int r[N],m[N],n; // x = r[i] (mod m[i])
		int L=1; // 所有模数的lcm
        void clear()
        {
            n=0; FK(r); FK(m); no_sol=0; L=1;
        }
        void add(int a,int b,int mm) // 加入一条方程 ax=b (mod m)
        // a<=1e6, b<=1e12, mm<=1e12
        {
            if (mm==1) return; // 我们跳过模数为1的方程
            int g=gcd(a,mm);
            if (b%g) {no_sol=1; return;}
            a/=g; b/=g; mm/=g;
            b=smul(b,inv(a,mm),mm);  // 小心!
            ++n;
            r[n]=b; m[n]=mm;
            L=lcm(L,m[n]);
        }
        int get_sol() // 解
        {
            if (n==0) {return 0;} // 有一个sb情况就是, 所有模数都为1, 不过不判好像没事
            if (no_sol) {return -1;}
            int R=r[1],M=m[1]; // M<=1e12, R<M
            F(i,2,n)
            {
                int x,y;
                int m2=lcm(M,m[i]);
                if (sol(M,-m[i],x,y,r[i]-R,m2))
                {
					R=(R+smul(x,M,m2))%m2; // 小心!
                    M=m2;
                }
                else return -1;
            }
            return R;
        }
    }

    multiset<int>sw; multiset<int>::iterator it,it2;
    void Sakuya()
    {
        exCRT::clear(); sw.clear();
        F(i,1,m) sw.insert(h[i]);
        int mn=0;
        // 注意到我们的每次攻击都至少要把龙打到0血以下,因此我们的x值是有下界的
        F(i,1,n)
        {
            it2=sw.upper_bound(a[i]);
            if (it2!=sw.begin()) --it2;
            int atk=*it2; // 找剑

            exCRT::add(atk,a[i],p[i]);
            mn=max(mn,(a[i]+atk-1)/atk); // 要打到0以下

            sw.erase(it2); sw.insert(g[i]);
        }
        int R=exCRT::get_sol(),M=exCRT::L;
        if (R==-1)
        {
            puts("-1");
        }
        else
        {
            if (R<mn) R=R+M*(mn-R+M-1)/M; // 这个其实就是不断的加, 直到加过下界
            printf("%lld\n",R);
        }
    }
    void IsMyWife()
    {
        int t=I(); int Cas=0;
        while(t-->0)
        {
            ++Cas;
            Input(Cas);
            Sakuya();
        }
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    return 0;
}

数据生成器:

这个代码暂时不在我手上,我明天去学校里搞吧。

posted @ 2021-10-13 22:34  Flandre-Zhu  阅读(30)  评论(1编辑  收藏  举报