2019杭电多校第五场

1001 fraction

题意是求 b b b使得 a b ≡ x ( m o d \frac{a}{b} \equiv x (mod bax(mod p ) p) p)
引入一个数 y y y变形成 a = b x − p y a=bx-py a=bxpy
考虑到 a a a的范围是 0 &lt; a &lt; b 0&lt;a&lt;b 0<a<b,把上式代入就可以得到 p x &lt; b y &lt; p x − 1 \frac{p}{x}&lt;\frac{b}{y}&lt;\frac{p}{x-1} xp<yb<x1p,然后求一个最小的 b b b
这是一个经典的问题(然而我并没有见过)
如果这个区间跨越了一个整数 d d d(且是最小的),那么 b y = d 1 \frac{b}{y}=\frac{d}{1} yb=1d,因为 b b b不可能再小了
否则,不等式通通减去 d − 1 d-1 d1,这个时候两边的数应该都是小于1的,取个倒数继续递归就好了
for example:11 7
( 11 7 , 11 6 ) = &gt; ( 4 7 , 5 6 ) = &gt; ( 6 5 , 7 4 ) = &gt; ( 1 5 , 3 4 ) = &gt; ( 4 3 , 5 1 ) = &gt; s o l v e d (\frac{11}{7},\frac{11}{6})=&gt;(\frac{4}{7},\frac{5}{6})=&gt;(\frac{6}{5},\frac{7}{4})=&gt;(\frac{1}{5},\frac{3}{4})=&gt;(\frac{4}{3},\frac{5}{1})=&gt;solved (711,611)=>(74,65)=>(56,47)=>(51,43)=>(34,15)=>solved
类似于辗转相除法,然后再反着套回去就好了

#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 200005
#define ll long long
using namespace std;
int T;
ll p,x,y,b;
void solve(ll a,ll b,ll &x,ll &y,ll c,ll d)
{
    //cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
    ll p = a / b;
    if (a % b) p++;
    if (c / d >= p) {x = p; y = 1; return;}
    a = a - b * (p - 1);
    c = c - d * (p - 1);
    solve(d,c,y,x,b,a);
    x = x + y * (p - 1);
}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%lld%lld",&p,&x);
        solve(p,x,b,y,p,x-1);
        printf("%lld/%lld\n",b*x-p*y,b);
    }
    return 0;
}

1002 three arrays

很好奇这个题为什么这么多人a掉了。。
首先一个显然的贪心是,每次找到最小的一对取出来,删掉这一对后继续做
一开始想了一个比较暴力的做法,就是维护每个 a i a_i ai对应的最小的 b i b_i bi,每次去掉一对后要更新所有的对应关系
感觉对于随机数据没啥问题?后来想想应该可以构造一组数据使得需要大量更新对应关系
(然后成功猜中被出题人公开处刑的错误做法)
(其实距离正解挺近了)
正解的做法是,不断找到对应的最小值: a 1 = &gt; b 1 = &gt; a 2 = &gt; b 2 = &gt; . . . . a_1=&gt;b_1=&gt;a_2=&gt;b_2=&gt;.... a1=>b1=>a2=>b2=>....
然后最后一定能回到前一个值,比如 b 2 b_2 b2对应的最小值如果已经出现过,那么一定是 a 2 a_2 a2
所以每个元素出入栈各一次,也只会在字典树上跑一次,复杂度可以接受
然后简单证明一下这个结论的正确性:
结论1: 如 果 a x 对 应 b y 如果a_x对应b_y axby b y 也 对 应 a x b_y也对应a_x byax,那么这一对一定是答案
证明:如果不是,那么通过交换成上述情况一定更优
结论2:把上述对应关系看作是单向边,则不存在长度大于2的环(也就是前面说的一定能回到前一个值)
证明: x − &gt; y − &gt; z x-&gt;y-&gt;z x>y>z可以得出x xor y大于y xor z(否则 y − &gt; x y-&gt;x y>x),然后一直绕绕绕最后就会绕回 x − &gt; y x-&gt;y x>y x y xy xy匹配大于 x y xy xy,矛盾,所以最多只会出现两个元素的环
然后呢我一开始使用递归的写法写的,有点难处理数字重复的情况,用queuestack疯狂mle,用listvector就开始tle。。常数是真的大
最后改了很多常数才刚好卡进去
后来发现读入优化挺慢的。。

#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 200005
using namespace std;
int tot[2],ls[N*31][2],rs[N*31][2],lnum[N*31][2],rnum[N*31][2],root[2];
int a[N],b[N],res[N],stck[N],bits[N];
int T,n,i,pt,resnum,x,y,temp;
void insert(int v,int op)
{
    int pos = 1; int i;
    fd(i,29,0)
    {
        int bs = (v >> i) & 1;
        if (bs == 0)
        {
            if (ls[pos][op] == 0) ls[pos][op] = ++tot[op];
            lnum[pos][op]++;
            pos = ls[pos][op];
        }   else
        {
            if (rs[pos][op] == 0) rs[pos][op] = ++tot[op];
            rnum[pos][op]++;
            pos = rs[pos][op];
        }
    }
}
int find(int v,int op)
{
    int pos = 1; int res = 0; int i;
    fd(i,29,0)
    {
        int bs = (v >> i) & 1;
        if (bs == 0)
        {
            if (lnum[pos][op] > 0) pos = ls[pos][op];
            else if (rnum[pos][op] > 0) {pos = rs[pos][op]; res += 1 << i;}
            else return -1;
        }   else
        {
            if (rnum[pos][op] > 0) {pos = rs[pos][op]; res += 1<< i;}
            else if (lnum[pos][op] > 0) pos = ls[pos][op];
            else return -1;
        }
    }
    return res;
}
void del(int v,int op)
{
    int pos = 1; int i;
    fd(i,29,0)
    {
        int bs = (v >> i) & 1;
        if (bs == 0)
        {
            if (lnum[pos][op] > 0) {lnum[pos][op]--; pos = ls[pos][op];}
            else {rnum[pos][op]--; pos = rs[pos][op];}
        }   else
        {
            if (rnum[pos][op] > 0) {rnum[pos][op]--; pos = rs[pos][op];}
            else {lnum[pos][op]--; pos = ls[pos][op];}
        }
    }
}
#include<cctype>
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int main()
{
    scanf("%d",&T);
    //T=read();
    while (T--)
    {
        scanf("%d",&n);
        fo(i,1,n) scanf("%d",&a[i]);
        fo(i,1,n) scanf("%d",&b[i]);
        //n=read();
        fo(i,1,n*31) lnum[i][0] = lnum[i][1] = rnum[i][0] = rnum[i][1] = 0;
        fo(i,1,n*31) ls[i][0] = ls[i][1] = rs[i][0] = rs[i][1] = 0;
        tot[0] = tot[1] = 1;
        //fo(i,1,n) a[i]=read();
        //fo(i,1,n) b[i]=read();
        fo(i,1,n) insert(a[i],0);
        fo(i,1,n) insert(b[i],1);
        pt = 0; resnum = 0;
        fo(i,1,n)
        {
            int tt = find(0,0);
            if (tt == -1) break;
            pt++; stck[pt] = tt;
            while (pt > 0)
            {
                temp = stck[pt];
                if (bits[pt] == 0)
                {
                    x = temp;
                    y = find(x,1);
                    if (pt >= 2 && stck[pt-1] == y)
                        {
                            resnum++; res[resnum] = stck[pt] ^ stck[pt-1];
                            del(stck[pt],0);
                            del(stck[pt-1],1);
                            pt -= 2;
                        }   else
                        {
                            pt++; stck[pt] = y; bits[pt] = 1;
                        }
                }   else
                {
                    y = temp;
                    x = find(y,0);
                    if (pt >= 2 && stck[pt-1] == x)
                    {
                        resnum++; res[resnum] = stck[pt] ^ stck[pt-1];
                        del(stck[pt-1],0);
                        del(stck[pt],1);
                        pt -= 2;
                    }   else
                    {
                        pt++; stck[pt] = x; bits[pt] = 0;
                    }
                }
            }
        }
        sort(res+1,res+n+1);
        fo(i,1,n-1) printf("%d ",res[i]); printf("%d\n",res[n]);
    }
    return 0;
}

1004 equation

考虑从左到右枚举答案的区间,那么左边的方程一定是取负,右边的方程一定是取正
一边扫描一边计算答案就好
还有就是输出答案为 0 0 0的时候要输出 0 / 1 0/1 0/1(分母忘记置0了,一字节惨案)

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MOD 998244353
#define N 100005
#define ll long long
using namespace std;
struct www{ll a,b;} f[N],g[N];
ll T,n,i,k,P,Q,C,flag;
ll res1[N],res2[N];
bool cmp(const www &x,const www &y) {return x.b*y.a > x.a*y.b;}
bool ck(ll P,ll Q,ll x,ll y)
{
    if (Q * x <= P * y) return true; else return false;
}
bool check(ll P,ll Q,ll x,ll y)
{
    if (P < 0) {P *= -1; Q *= -1;}
    if (x != -1)
    {
        if (ck(g[x].a,-g[x].b,P,-Q) == false) return false;
    }
    if (y != n + 1)
    {
        if (ck(P,-Q,g[y].a,-g[y].b) == false) return false;
    }
    return true;
}
ll get_gcd(ll x,ll y)
{if (x % y == 0) return y; else return get_gcd(y,x%y);}
int main()
{

    scanf("%lld",&T);
    while (T--)
    {
        scanf("%lld%lld",&n,&C);
        fo(i,1,n) scanf("%lld%lld",&f[i].a,&f[i].b);
        sort(f+1,f+n+1,cmp);
        k = 0; i = 1;
        while (i <= n)
        {
            k++; g[k].a = f[i].a; g[k].b = f[i].b; i++;
            while (i <= n)
            {
                if (g[k].b*f[i].a==g[k].a*f[i].b)
                {
                    g[k].a += f[i].a; g[k].b += f[i].b;
                    i++;
                } else break;    
            }
        }
        P = Q = 0; n = k; Q = -C;
        fo(i,1,n) {P -= g[i].a; Q -= g[i].b;}
        if (P == 0 && Q == 0) {cout<<-1<<endl; continue;}
        k = 0;
        if (P != 0) 
            if (check(P,Q,-1,1)) {k++; res1[k] = P; res2[k] = -Q;}
        flag = 0;
        fo(i,1,n)
        {
            P += 2 * g[i].a; Q += 2 * g[i].b;
            if (P == 0 && Q == 0) {flag = 1; break;}
            if (P != 0)
                if (check(P,Q,i,i+1)) {k++; res1[k] = P; res2[k] = -Q;}
        }
        if (flag == 1) cout<<-1<<endl; else
        if (k == 0) cout<<0<<endl; else
        {
            fo(i,1,k) f[i].a = res1[i],f[i].b = res2[i];
            fo(i,1,k) if (f[i].a < 0) {f[i].a *= -1; f[i].b *= -1;}
            fo(i,1,k) f[i].b *= -1;
            sort(f+1,f+k+1,cmp);
            fo(i,1,k) f[i].b *= -1;
            n = k; k = 0; i = 1;
            while (i <= n)
            {
                k++; g[k].a = f[i].a; g[k].b = f[i].b; i++;
                while (i <= n)
                {
                    if (g[k].b*f[i].a==g[k].a*f[i].b)
                    {
                        g[k].a += f[i].a; g[k].b += f[i].b;
                        i++;
                    } else break;    
                }
            }
            cout<<k;
            if (k == 0) cout<<endl; else cout<<" ";
            fo(i,1,k)
            {
                P = g[i].a; Q = g[i].b;
                ll f = 1;
                if (P < 0) {f *= -1; P *= -1;}
                if (Q < 0) {f *= -1; Q *= -1;}
                if (f == -1) cout<<"-";
                ll gcd;
                if (Q == 0) gcd = P; else gcd = get_gcd(P,Q);
                cout<<Q/gcd<<"/"<<P/gcd;
                if (i == k) cout<<endl; else cout<<" ";
            }
        }
    }
    return 0;
}

1005 permutation 1

n ≤ 9 n \le 9 n9时暴力即可
n &gt; 9 n &gt; 9 n>9时,考虑答案的形式
字典序最小的情况是 n , 1 , 2 , 3..... n,1,2,3..... n,1,2,3.....,然后呢,找找规律就可以发现前面若干个答案一定是 n , 1 , 2..... n,1,2..... n,1,2.....的形式,因为最前面那些数最小了
实际上当 n &gt; 9 n&gt;9 n>9时,由于 8 ! &gt; 10000 8!&gt;10000 8!>10000,所以最多只会动最后8位,所以对后8位全排列即可
再然后,如果你打表找找规律就能发现,后8位的排列第k小就是字典序第k小(直接暴力排序也可以)

1006 string matching

题目给的程序统计的是从每个位置出发,和开头匹配的长度
也就是后缀和原串的公共前缀长度
直接把kmp算法的next数组统计一下就好
注意一下fail的时候的+1

#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int T,m,i,j,k,len,l;
long long res;
char a[1000005];
int nxt[1000005];
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%s",a); m = strlen(a);
        for (j = 0;1 + j < m && a[j] == a[1+j]; j++);
        nxt[1] = j; k = 1;
        for (i = 2;i < m; i++)
        {
            len = k + nxt[k]; l = nxt[i-k];
            if (l < len - i)
                nxt[i] = l;
            else
                {
                    for (j = max(0,len-i);i+j<m && a[j] == a[i+j]; j++);
                    nxt[i] = j; k = i;
                }
        }
        res = 0;
        //fo(j,1,m-1) cout<<nxt[j]<<" "; cout<<endl;
        fo(j,1,m-1)
            if (nxt[j] == m - j) res = res + nxt[j]; else res = res + nxt[j] + 1;
        cout<<res<<endl;
    }
    return 0;
}

1007 permutation 2

把题目要求看作是从 x x x走到 y y y且经过所有点
首先一个显然的结论是,一条路总体上最多只能走两遍
什么意思呢,比如从1走到n,那么要么是1直接到n,要么是1跳着走到n,再跳着走回1
回到原题,不妨令 x &lt; y x&lt;y x<y,那么答案一定是 x = &gt; 1 = &gt; x + 1 = &gt; y − 1 = &gt; n = &gt; y x=&gt;1=&gt;x+1=&gt;y-1=&gt;n=&gt;y x=>1=>x+1=>y1=>n=>y
而显然 x = &gt; 1 = &gt; x + 1 x=&gt;1=&gt;x+1 x=>1=>x+1的走法是唯一的,来回必须刚好间隔,另一边同理
所以不同的方案只会出现在 x + 1 = &gt; y − 1 x+1=&gt;y-1 x+1=>y1
因为可以 12 12 12走,也可以 1324 1324 1324走,所以有 f [ i ] = f [ i − 1 ] + f [ i − 3 ] f[i]=f[i-1]+f[i-3] f[i]=f[i1]+f[i3]
注意一下当 x = 1 x=1 x=1时不需要加一,同理 y = n y=n y=n时不需要减一

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MOD 998244353
#define N 100005
using namespace std;
int NN,i,n,x,y,T;
int f[N];
int main()
{
    scanf("%d",&T);
    NN = 100000;
    f[0] = 1; f[1] = 1; f[2] = 1;
    fo(i,3,NN) f[i] = (f[i-1] + f[i-3]) % MOD;
    fo(i,1,T)
    {
        scanf("%d%d%d",&n,&x,&y);
        if (x > y) swap(x,y);
        if (x != 1) x++;
        if (y != n) y--;
        if (x > y) cout<<0<<endl; else 
        cout<<f[y-x]<<endl;
    }
    return 0;
}

1008 line symmetric

答题思路都是枚举对称轴所在的位置,我的想法和std一样,要么 a i 和 a i − 1 a_i和a_{i-1} aiai1作为对称点,要么 a i 和 a i − 2 a_i和a_{i-2} aiai2作为对称点,队友的做法是,分别美剧枚举 a 1 a_1 a1 a 2 a_2 a2 a 、 3 a、3 a3和所有点(要排除点在对称轴上和坏点的情况)
不过check的时候有一个最致命的非法情况没有考虑到:当前的两个点不但要关于对称轴对称,还得在同侧,也就是说在从对称轴左侧出发找的点必须始终在左侧,否则就会出现非简单多边形的情况
然后我们一直在判断答案非法的情况。。。。想了无数种特殊情况还是搞不出来
实际上所有的非法情况归结起来就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值