UVA12096 题解

Posted Illumina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UVA12096 题解相关的知识,希望对你有一定的参考价值。

这道题虽然被评黄,但个人感觉不止普及组难度,而且是一道很有价值的题目。题解区里全是 \\(O(n^2logn)\\) 的 STL 大法,我来发一篇 \\(O(n^2)\\) 哈希做法。目前 0ms 喜提最优解。

这道题是 codeforces gym 的题目,本质上就是模拟栈中集合插入,复制,相加,取交集,取并集的过程。注意,这些集合都是不可重集。 如果你直接把这些括号和逗号存下来肯定是不行的,因为它是一个一层套一层的结构,不好直接维护。况且经过复制,括号总数会指数级增长。

既然直接存储不行,我就想用哈希来表示每个集合里的元素。这样每个集合就是一个 vector,里面存储了每个元素对应的哈希值。比如集合 , , 的 vector 里存储 和 , 这两个字符串的哈希。

下面考虑分别维护这五种操作。

  1. 对于 PUSH 操作,直接新建一个 vector,里面什么也不存推入栈中。

  2. 对于 DUP 操作,直接复制一遍栈顶的 vector 推入栈中。

  3. 对于 UNION 操作,我们需要把两个 vector 里的数全部放入新的 vector 内,并且去重。如果这两个 vector 无序,去重需要 \\(O(nlogn)\\) 的复杂度,是不能接受的。所以我们在维护 vector 里的哈希值时,不妨使其有序。 这样,在做 UNION 操作时,就可以归并排序去重了,复杂度 \\(O(n)\\)

  4. 对于 INTERSECT 操作,和 UNION 一样归并排序找到重复元素即可。

  5. 对于 Add 操作,这是最复杂的一个操作。记第一个 vector 为 \\(v1\\),第二个 vector 为 \\(v2\\)。则我们要把 \\(v1\\) 这个整体作为一个元素,求出其哈希值并放入 \\(v2\\)。但注意:不能设计普通的按位乘法哈希。 至于为什么留给大家自己思考。(提示:括号总数巨大)

    虽然普通哈希不行,但我们有一个很好的性质,就是 v1 内的哈希值是有序的,唯一确定的。于是我们可以把哈希设计成这样:设 v1 内的哈希值为 \\(h1\\)\\(h2\\)\\(h3\\),则有:

    \\(H(v1)=((h1+\\Delta)\\times h2+\\Delta)\\times h3\\%P\\)

    其中 \\(\\Delta\\)\\(P\\) 是自己设定的常数。注意计算哈希时还要在每个元素之间加一个逗号,左右两边加上大括号。然后把 \\(H(v1)\\) 用插入排序的方法移到 \\(v2\\) 相应位置。我这里使用了 vector 的 insert 函数,比插入排序快几十倍左右。

最后输出栈顶 vector 的大小即可。

代码:

#include<iostream>
#include<vector>
#include<algorithm>
#define f first 
#define s second
#define gc getchar
#define pc putchar
#define pb push_back
#define sz(a) a.size()
#define st(a) a.begin()
#define ed(a) a.end()
#define all(a) st(a),ed(a)
#define las(a) *a.rbegin()
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<class Typ> Typ &Rd(Typ &x)
    x=0; char ch=gc(),sgn=0;
    for(;ch<\'0\'||ch>\'9\';ch=gc()) sgn|=ch==\'-\';
    for(;ch>=\'0\'&&ch<=\'9\';ch=gc()) x=x*10+(ch^48);
    return sgn&&(x=-x),x;

template<class Typ> void Wt(Typ x)
    if(x<0) pc(\'-\'),x=-x;
    if(x>9) Wt(x/10);
    pc(x%10^48);

const int N=2005;
const int Ad=1000099;
const int Md=998244353;
vector<int> Hs[N]; char op[10];
int cas,n,stk[N],top;
int main()
    for(Rd(cas);cas;cas--)
        Rd(n),top=0;
        for(int i=1;i<=n;i++)
            Hs[i].clear(),scanf("%s",op+1);
            if(op[1]==\'P\') stk[++top]=i;
            else if(op[1]==\'D\')
                for(auto j:Hs[stk[top]]) Hs[i].pb(j);
                stk[++top]=i;
            else if(op[1]==\'U\')
                int id1=stk[top--],id2=stk[top--],ps=0;
                for(auto cur:Hs[id1])
                    for(;ps<sz(Hs[id2])&&Hs[id2][ps]<cur;ps++)
                        Hs[i].pb(Hs[id2][ps]);
                    Hs[i].pb(cur);
                    if(ps<sz(Hs[id2])&&Hs[id2][ps]==cur) ps++;
                
                for(;ps<sz(Hs[id2]);ps++)
                    Hs[i].pb(Hs[id2][ps]);
                stk[++top]=i;
            else if(op[1]==\'I\')
                int id1=stk[top--],id2=stk[top--],ps=0;
                stk[++top]=i;
                for(auto cur:Hs[id1])
                    while(ps<sz(Hs[id2])&&Hs[id2][ps]<cur) ps++;
                    if(ps<sz(Hs[id2])&&Hs[id2][ps]==cur) Hs[i].pb(cur);
                
            else
                int Hsh=1+Ad,id1=stk[top--],id2=stk[top];
                for(auto cur:Hs[id1])
                    if(Hsh!=1+Ad) Hsh=((ll)Hsh*2%Md+Ad)%Md;
                    Hsh=((ll)Hsh*cur%Md+Ad)%Md;
                
                Hsh=((ll)Hsh*3%Md+Ad)%Md;
                if(!count(all(Hs[id2]),Hsh))
                    Hs[id2].insert(lower_bound(all(Hs[id2]),Hsh),Hsh);
            
            Wt(sz(Hs[stk[top]])),pc(\'\\n\');
        
        puts("***");
    
    return 0;

写在最后:

要真的在联赛出这道题,我说不定就不选这种方法了,虽然保险,但 STL 也有优势,就是简单好写好调试。(而且 CCF 的数据不用多虑)不管怎么说,看到题解区同情 p 党,我也算是 python 的一员,哈希法也算是给 p 党一个可行的尝试方法了,只要把 vector 换成 list 即可。

做黄题让我回想起初一的时光,一去不复返了。也许有一天 OI 会从我脑中淡漠,但那段回忆永远藏着我的心中。

题解Street Numbers [UVA138]

【题解】Street Numbers [UVA138]

传送门:( ext{Street Numbers [UVA138]})

【题目描述】

求满足 (n<m)(sum_{i=1}^{n-1}i=sum_{i=n+1}^{m}i) 的正整数对 (n,m) 。从小到大输出前 (10) 组解,要求输出的每个整数都占 (10) 格宽。

【分析】

被抄代码+抄题解的xxs们气到了,于是写下这篇题解,顺便简单讲一下佩尔方程(好像在 ( ext{OI}) 里运用不多)

佩尔方程是一种不定二次方程,且分为两类,该种方程有无穷多个解 【证明】

【第一类佩尔方程】

形如 (x^2-Dy^x=1) ((Dinmathbb{N^{*}} ext{且为非平方数}))

设它的一组最小正整数解为 (left{egin{array}{c}x=x_0\y=y_0end{array} ight.,) 则其第 (n) 个解满足:(x_n+sqrt{D}y_n=(x_0+sqrt{D}y_0)^{n+1})

(sqrt{D}) 这个东西并不友好,考虑转换成递推式:

[egin{aligned}x_n+sqrt{D}y_n&=(x_0+sqrt{D}y_0)(x_0+sqrt{D}y_0)^n&=(x_0+sqrt{D}y_0)(x_{n-1}+sqrt{D}y_{n-1})&=(x_0x_{n-1}+Dy_0y_{n-1})+sqrt{D}(x_0y_{n-1}+y_0x_{n-1})end{aligned}]

即可得:(left{egin{array}{c}x_n=x_0x_{n-1}+Dy_0y_{n-1}\y_n=x_0y_{n-1}+y_0x_{n-1}end{array} ight.)

【第二类佩尔方程】

形如 (x^2-Dy^x=-1) ((Dinmathbb{N^{*}} ext{且为非平方数}))

设它的一组最小正整数解为 (left{egin{array}{c}x=x_0\y=y_0end{array} ight.,) 则其第 (n) 个解满足:(x_n+sqrt{D}y_n=(x_0+sqrt{D}y_0)^{2n+1})

求递推式就把右边化成 ((x_0+sqrt{D}y_0)^{2}(x_{n-1}+sqrt{D}y_{n-1})) 再展开,后续推导略。

【求解方法】

第一步是求最小正整数解。可以证明当 (D) 较小时 (x_0,y_0) 也较小,所以直接从小到大暴力枚举即可。算出 (x_0,y_0) 后即可递推得到其他解。

求解佩尔方程通常会以 高精递推/矩阵递推加速 的形式出现(虽然也没多少题)。

回到这题,先化简柿子:(frac{n(n-1)}{2}=frac{m(m+1)}{2}-frac{n(n+1)}{2}Longrightarrow 2n^2=m^2+m),显然和上面的方程没有丝毫关联,我们给两边同乘 (4) 然后配方:(8n^2=4m^2+4mLongrightarrow (2m+1)^{2}-8n^2=1),忽略 (n<m) 的条件,显然为第一类佩尔方程。

瞪眼法暴力枚举法可知其最小解为 (left{egin{array}{c}x_0=3\y_0=1end{array} ight.)

答案为 (left{egin{array}{c}n_i=y_i\m_i=frac{x_i-1}{2}end{array} ight.)

(输出来后发现除了第 (0) 个解以外其他均满足 (n<m)

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=103;
int D,x[N],y[N];
int main(){
    x[0]=3,y[0]=1,D=8;
    for(Re i=1;i<=10;++i)
        x[i]=x[0]*x[i-1]+D*y[0]*y[i-1],
        y[i]=x[0]*y[i-1]+y[0]*x[i-1];
    for(Re i=1;i<=10;++i)//因为要满足n<m,(x0,y0)被舍去了 
        printf("%10d%10d
",y[i],x[i]-1>>1);
}

以上是关于UVA12096 题解的主要内容,如果未能解决你的问题,请参考以下文章

UVA 12096 The SetStack Computer

Uva 12096.The SetStack Computer

UVA12096 - The SetStack Computer(set + map映射)

UVa - 12096 集合栈计算机(STL)

The SetStack Computer UVA - 12096

uva 12096 The SetStack Computer(STL set)