2021杭电多校第一场

1007: Pass!
1 0
0 1
10 2
90 3
910 4
9090 5
90910 6
909090 7
9090910
90909090
909090910
\(f_i = f_{i-1} * 9 + f_{i-2} * 10\)
\(f_i + f_{i-1} = 10^x\)

\(if(奇) f_i = (f_{i-1} - 1) * 10\)
\(else f_i = (f_{i-1} + 1) * 10\)
1、假设x是偶数项 \(-> x + (x - 1) * 10 = 10^m\)\((m & 1 == 0)\)
2、假设x是奇数项 \(-> x + (x + 1) * 10 = 10^m\)\((m & 1 == 1)\)
通用即将10改为(n - 1)

TLE:
优化
1、偶数项优化:即 \(x\) + \((x - 1)\) * 10 = \(100^m\) -> \(ans = 2 * m\)
2、奇数项优化:变为 \(x\) * \(inv(n - 1) + 1 + x\) = \(100^m\) -> \(ans = 2 * m + 1\)

还是不行
优化:将两项放在一起 -> 显然两项都是\(100^m\),一起求

还是不行
优化:将\(map\) -> \(unordered_map\)

ok!
BGSG:

const int mod = 998244353;
inline int ksm(int a, int b) {
    int res = 1;
    while(b) {
        if(b & 1)   res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
inline int inv(int x) {
    return ksm(x, mod - 2);
}

int log(int a, int b, int ans1, int ans2) {
    int m = sqrt(mod + 0.5), v = inv(ksm(a, m));
    unordered_map<int, int> mp;   mp[1] = 0;
    for(int i = 0, e = 1; i < m; ++ i, e = e * a % mod)  {
        if(e == ans1)   return i * 2;
        if(e == ans2)   return i * 2 + 1;
        if(!mp.count(e))    mp[e] = i;
    }
    for(int i = 0, mul = 1; i < m; ++ i, mul = mul * v % mod) {
        if(mp.count(ans1 * mul % mod))  return (i * m + mp[ans1 * mul % mod]) * 2;
        if(mp.count(ans2 * mul % mod))  return (i * m + mp[ans2 * mul % mod]) * 2 + 1;
    }
    return -1;
}
void run() {
    //printf("%lld\n", ksm(10, 196003445));
    int n = rd(), x = rd(), pre = 1;
    /*for(int i = 1; i <= 10; ++ i) {
        if(i & 1)   printf("%lld\n", pre = (pre - 1) * (n - 1));
        else        printf("%lld\n", pre = (pre + 1) * (n - 1));
    }*/
    if(x == 1)  {
        puts("0");
        return ;
    }
    if(x == 0) {
        puts("1");
        return ;
    }
    int y = (x - 1) * (n - 1) % mod;
    int ans1 = (x + y) % mod;
    //x = (x + 1) * (n - 1) % mod;
    //(prex - 1) * (n - 1) = x
    //prex = x / (n - 1) + 1
    y = (x * ksm(n - 1, mod - 2) + 1) % mod;
    int ans2 = (x + y) % mod;
    int res = log((n - 1) * (n - 1) % mod, (x + y) % mod, ans1, ans2);//偶数项
    printf("%lld\n", res % mod);
    /*x = (x + 1) * (n - 1) % mod;
    y = (x - 1) * (n - 1) % mod;
    res = log((n - 1) * (n - 1) % mod, (x + y) % mod);//奇数项
    if(res != -1) printf("%lld\n", (res * 2 % mod - 1 + mod) % mod);
    else puts("-1");*/
    return ;
}
signed main() {
    int t = rd();
    while(t--) run();
    return 0;
}

1010: zoto
\(x\)轴莫队,对\(y\)轴维护一个分块
这样修改的复杂度是\(O(1 * \sqrt n)\)的,查询的复杂度是$O(\sqrt n) $的
三维偏序 CDQ咋做呀

const int maxn = 1e5 + 10;
int n, m, sz, bnum;
int a[maxn], ans[maxn], pos[maxn];
struct query{
    int l, r, y1, y2, id;
    bool operator < (const query & a) const {
        return (pos[l] ^ pos[a.l]) ? pos[l] < pos[a.l] : ((pos[l] & 1) ? r < a.r : r > a.r);
    }
}q[maxn];

int num[maxn], sum[maxn];
inline void add(int x) {
    (!num[x]++)&&(++sum[x / sz]);
}
inline void del(int x) {
    (!--num[x])&&(--sum[x / sz]);
}
int calc(int x) {
    int res = 0;
    for(int i = 0; i < x / sz; ++ i)   res += sum[i];
    for(int i = x / sz * sz; i <= x; ++ i)  res += num[i] ? 1 : 0;
    return res;
}
void run() {
    n = rd(), m = rd();
    memset(num, 0, sizeof num);
    memset(sum, 0, sizeof sum);
    sz = sqrt(n), bnum = ceil(1.0 * n / sz);
    for(int i = 1; i <= bnum; ++ i) for(int j = (i - 1) * sz + 1; j <= i * sz; ++ j)    pos[j] = i;
    for(int i = 1; i <= n; ++ i)    a[i] = rd();
    for(int i = 1; i <= m; ++ i)
        q[i].l = rd(), q[i].y1 = rd(), q[i].r = rd(), q[i].y2 = rd(), q[i].id = i;
    sort(q + 1, q + 1 + m); int l = 1, r = 0;
    for(int i = 1; i <= m; ++ i) {
        while(l < q[i].l)   del(a[l++]);
        while(l > q[i].l)   add(a[--l]);
        while(r < q[i].r)   add(a[++r]);
        while(r > q[i].r)   del(a[r--]);
        ans[q[i].id] = calc(q[i].y2) - calc(q[i].y1 - 1);
    }
    for(int i = 1; i <= m; ++ i)    printf("%lld\n", ans[i]);
}

补上三维偏序的做法
把种类查询转换偏序查询的常规操作:指向上一个出现的位置当且仅当上一次出现的位置在区间外面时计算贡献
对于每个询问的右端点可以看作一个时间轴,如果该点可能对询问产生贡献,那么下一个这个点的横坐标严格大于询问右端点。
转化为平面二维数点,然后差分+bit即可统计
同理其实对每个询问的左端点也可以看作一个时间轴,如果该带点可能对询问产生贡献,那么前一个这个点的横坐标要严格小于询问的左端点。
本质都是种类\(-》\)​偏序

首先我们来看,给的坐标为x,y,y的限制是不能重复统计,如果y可以重复统计,那么显然这是一个二维的偏序,通过一个归并或者树状数组即可解决,对于y不能重复,再添一维区间的右端点作为时间轴,x,y比我小但是时间小于我这个时间的我不要,也就是说给哪些y相同的数一个时间,那么从后往前给时间,最后一个的时间当然可以给最大的,也就是它后面没有y来阻碍它了,那么对于前面的y,依次给后一个y的下标即可,这样就不会被重复统计了。非常强大的转换


const int N=100010;
int a[N],n,m,ans[N],last[N];
int nex[N],pos[N];
struct node{
    int op;
    int a, b, c;//三维偏序
    int sgn, id;
}q[N << 4];
struct BIT{
    int bit[N];
    void update(int k, int v) {
        for(; k <= n + 1; k += k & -k)  bit[k] += v;
    }
    int query(int k) {
        int res = 0;    for(; k > 0; k -= k & -k)   res += bit[k];  return res;
    }
}T;

void solve(int l, int r) {
    if(l >= r)  return ;
    int mid = l + r >> 1, pl = l;
    solve(l, mid);  solve(mid + 1, r);
    for(int pr = mid + 1; pr <= r; ++ pr) {
        while(pl <= mid && q[pl].b <= q[pr].b) if(q[pl++].op == 0)    T.update(q[pl - 1].c, 1);
        if(q[pr].op == 1)    {
            dbg(q[pr].a, q[pr].b, q[pr].c, q[pr].sgn * (T.query(n + 1) - T.query(q[pr].c)));
            ans[q[pr].id] += q[pr].sgn * (T.query(n + 1) - T.query(q[pr].c));
        }
    }
    while(pl > l) if(q[--pl].op == 0)  T.update(q[pl].c, -1);
    inplace_merge(q + l, q + mid + 1, q + r + 1, [](const node &x, const node &y){return x.b < y.b || x.b == y.b && x.a < y.a;});
}
void run() {
    n = rd(), m = rd();
    for(int i = 1; i <= n; ++ i)    a[i] = rd();
    for(int i = 1; i <= m; ++ i)    ans[i] = 0;
    for(int i = 0; i <= 100000; ++ i)   pos[a[i]] = n + 1;

    for(int i = n; i >= 1; -- i) {
        nex[i] = pos[a[i]];
        pos[a[i]] = i;//nex就是下一个y = a[i]的下标
    }
    for(int i = 1; i <= n; ++ i)    dbg(i, a[i], nex[i]);
    int cnt = 0;
    for(int i = 1; i <= n; ++ i)    q[++cnt] = {0, i, a[i], nex[i]};//
    for(int i = 1; i <= m; ++ i) {
        int x0 = rd(), y0 = rd(), x1 = rd(), y1 = rd();
        q[++cnt] = {1, x0 - 1, y0 - 1, x1, 1, i};//类似二维差分,都对ans[i]有贡献
        q[++cnt] = {1, x0 - 1, y1, x1, -1, i};
        q[++cnt] = {1, x1, y0 - 1, x1, -1, i};
        q[++cnt] = {1, x1, y1, x1, 1, i};
    }
    sort(q + 1, q + 1 + cnt, [](const node &x, const node &y){return x.a < y.a || x.a == y.a && x.b < y.b;});
    for(int i = 1; i <= cnt; ++ i)  dbg(q[i].op, q[i].a, q[i].b, q[i].c, q[i].sgn, q[i].id);
    solve(1, cnt);
    for(int i = 1; i <= m; ++ i)    printf("%d\n", ans[i]);
    return ;
}

更新树套树的写法
树状数组套动态开点权值线段树

对于树套树最简单的理解就是对于一个二维坐标系,需要询问其中一个矩形区块中有多少的点
我们可以把x轴作为第一维,通过树状数组维护,将y轴作为第二维,相当于树状数组的每个节点都是固定x的一条平行y轴的有长度的线段,长度就是值域,那么就要对每个节点建树,但是对于这棵树,不是满的,而且可能节点很少,所以要考虑通过动态开点来减小时间和空间的花销,通过动态开点的权值线段树来维护固定x的y的值域。这样就可以通过\(log^2\)复杂度查询和修改了。

const int maxn = 1e5 + 10;;
int a[maxn], n, m, ans[maxn], last[maxn];

struct TreeCover{
    struct query{
        int l, L, R, id;
    };
    vector<query> q[maxn];
    struct node{
        int lson, rson, v;
    }t[maxn * 200];
    int rt[maxn], cnt, lim;
    void update(int &rt, int l, int r, int pos, int v) {
        if(!rt) rt = ++ cnt;//动态开点
        t[rt].v += v;
        if(l == r)  return ;
        int mid = l + r >> 1;
        if(pos <= mid)  update(t[rt].lson, l, mid, pos, v);
        else            update(t[rt].rson, mid + 1, r, pos, v);
    }

    int query(int rt, int l, int r, int L, int R) {
        if(!rt)  return 0;
        if(l >= L && r <= R)    return t[rt].v;
        int mid = l + r >> 1, res = 0;
        if(L <= mid)    res += query(t[rt].lson, l, mid, L, R);
        if(R > mid)     res += query(t[rt].rson, mid + 1, r, L, R);
        return res;
    }

    void add(int k, int pos, int v) {
        for(; k <= n; k += k & -k)  update(rt[k], 0, lim, pos, v);
    }

    int ask(int k, int L, int R) {
        int res = 0;    for(; k; k -= k & -k)   res += query(rt[k], 0, lim, L, R);  return res;
    }

    void init() {
        for(int i = 0; i <= lim; ++ i)  last[i] = 0;
        for(int i = 0; i <= n; ++ i)    rt[i] = 0;
        for(int i = 0; i <= cnt; ++ i)  t[i].lson = t[i].rson = t[i].v = 0;
        cnt = 0;    for(int i = 0; i <= n; ++ i)    q[i].clear();
    }
}T;
signed main() {
    int _ = rd();
    while(_ --) {
        T.init();
        n = rd(), m = rd();
        for(int i = 1; i <= n; ++ i)    a[i] = rd();
        T.lim = *max_element(a + 1, a + 1 + n);
        for(int i = 1; i <= m; ++ i) {
            int x0 = rd(), y0 = rd(), x1 = rd(), y1 = rd();
            T.q[x1].push_back({x0, y0, y1, i});//询问,按照右端点存储
        }

        for(int i = 1; i <= n; ++ i) {
            if(last[a[i]])  T.add(last[a[i]], a[i], -1);//去除重复贡献
            T.add(i, a[i], 1);    last[a[i]] = i;//标记这个节点
            for(auto it : T.q[i]) ans[it.id] = T.ask(i, it.L, it.R) - T.ask(it.l - 1, it.L, it.R);//L R是y的区间
        }
        for(int i = 1; i <= m; ++ i)    printf("%d\n", ans[i]);
    }
}

1006
01字典树,去在书上放前缀和,然后去维护每一个出现最右边的位置

//#include<bits/stdc++.h>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<set>
#include<complex>
//#include<unordered_map>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,b,a) for(int i=b;i>=a;i--)
#define m_p make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define pi acos(-1)
#define Io ios::sync_with_stdio(false);cin.tie(0)
#define io ios::sync_with_stdio(false)
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef  long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;
typedef complex<double> Comp;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double  eps=1e-9;
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline int lcm(int a,int b){return a*b/gcd(a,b);}
namespace  IO  
{
    const int len=4e7;char buf[len];int sz,p;
    void begin(){p=0;sz=fread(buf,1,len,stdin);}
    inline bool read(ll &x)
    {
        if (p==sz)return 0;int f=1,d=0;char s=buf[p++];
        while(s<'0'||s>'9'&&p<sz){if(s=='-') f=-1;s=buf[p++];}
        while(s>='0'&&s<='9'&&p<sz){d=d*10+s-'0';s=buf[p++];}
        x=f*d; return p!=sz;
    }
}
inline int rd()
{
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;
}
const int maxn=1e5+50;
int t[maxn * 32][2], a[maxn],tot,pos[maxn*32];
void run()
{
    int _ = rd();
    while(_--)
    {
        int n = rd(), k = rd();
        rep(i, 1, n) a[i] = rd(), a[i] ^= a[i - 1];
        tot = 1;
        t[1][0] = t[1][1] = 0, pos[1] = 1;
        int ansl = -1, ansr = n;
        rep(i, 0, n)
        {
            int u = 1, res = -1;
            dep(j,31,0)
            {
                int v = (a[i] >> j) & 1;
                if(!((k>>j)&1))
                {
                    if(t[u][v^1])
                        res = max(res, pos[t[u][v ^ 1]]);
                    u = t[u][v];
                }else
                    u = t[u][v ^ 1];
                if(u==0 )
                    break;
            }
            if(u)
                res = max(res, pos[u]);
            if(res>=0&&ansr-ansl>i-res)
                ansr = i, ansl = res;
            u = 1;
            dep(j,31,0)
            {
                int v = (a[i] >> j) & 1;
                if(!t[u][v])
                {
                    t[u][v] = ++tot;
                    pos[tot] = -1;
                    t[tot][0] = t[tot][1] = 0;
                }
                u = t[u][v];
                pos[u] = max(pos[u], i);
            }
        }
        if(ansl==-1)
            printf("-1\n");
        else
            printf("%d %d\n", ansl+1, ansr);
    }
}

int main()
{
   run();
   return 0;
}
/* 
start time:
over time:


2
3 2
1 2 2
9 7
3 1 3 2 4 0 3 5 1
*/

1009
我觉得题目的意思还是挺明确
也很容易想到差不多的方法,但是我始终不知道用最小生成树做有什么问题
思想实际上就是,去选取最小的k条边构成图,但是要注意的时相同长度的边,一定要还要继续的判断,
等于第k条的时候还要判断第k+1条,最后来判断。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+50;
    struct edge
    {
        int l,r,val;
        edge(){}
        edge(int l,int r,int val):l(l),r(r),val(val){}
        bool friend operator<(const edge& a,const edge& b)
        {
            return a.val<b.val;
        }
    }e[maxn];
    int fa[maxn];
    int n,m,k;
    int find(int x)
    {
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    
    int main()
    {
        int _;
        scanf("%d",&_);
        while(_--)
        {
             scanf("%d%d%d",&n,&m,&k);
            int maxx=0;
            for(int i=1;i<=n;++i) fa[i]=i;
             for(int i=1;i<=m;++i)
             {
                scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].val);             
                 maxx=max(maxx,e[i].val);
             }
             sort(e+1,e+1+m);
             int now=n,ans=-1;
            for(int i=1;i<=m;++i)
            {
                if(e[i-1].val!=e[i].val)
                {
                    if(now==k)
                    {
                        ans=e[i-1].val;
                        break;
                    }
                }    
                int fx=find(e[i].l),fy=find(e[i].r);
                if(fx==fy) continue;
                now--,fa[fx]=fy,ans=e[i].val;
            }         
            //cout<<ans<<endl;
             printf("%d\n",now==k?ans:-1);
         } 
        return 0;
    }

//1002:每次添加一个点,求以该点为圆心半径为r的圆中的权值和。\(~~~~~~~~~~~~~~~~\) KDT
//1003:要形成若干个没有公共边的环,充要条件是每个点的度数都是偶数。
//将点的度数限制与格子的限制结合起来,将每条边是否存在作为变量,
//可以列出\(2nm\)个异或方程,使用高斯消元求解即可。若常数过大可以用bitset优化。
//1004:求1e18的背包计数,物品n最多100个,权值最大10 \(~~~~~~~~~~~~~~~~~~~\) 多项式 拉格朗日插值
//1006:异或和操作 \(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\) 01字典树
//1007:求\(a^x = b\) \(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\) BSGS
//1008:求非递减最大矩阵面积 \(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\) 单调栈
//1009:最小生成树并查集思维
//1010:二维平面内给询问,求一个矩形内不同y的个数\(~~~~~~~~~~~~~~~~~~~~~~~~~~~\) 莫队+分块 CDQ+BIT
//1011:三色项链,约束为相邻不同色以及某种绿色最多m,翻转相同\(~~~~~~~~~~~~~~~~\) polya定理

posted @ 2021-07-20 20:59  wlhp  阅读(284)  评论(0)    收藏  举报