ACM散题习题库 1【持续更新】

收录了部分有趣的题目

【一句话,干就是了!】

目前学习进度:图论→匹配问题→背包/区间/状压DP→数论

  字符串

  题目一 : 2008年老题,但是网上没有题解【 vjudge 可以看  sourse code  】,最后被bs学长搞了。(第一次接触01BFS。

 

  暴力枚举:

  题目二 

  ①思路问题:因为最大值的变化性,一直写不出来【应该反思自己写代码的能力了】,后来看了题解,发现对于最大值选择与哪一个值匹配,我们是可以枚举的【TLE怕了,不敢写,不敢想】,但是n=1000是完全可以写的,而且题目还保证了:

【It is guaranteed that the total sum of nn over all test cases doesn't exceed 1000

  ②码力问题:我看到别人用multiset模拟时,我也试着写了一遍,后来发现一直异常。。【原因出在了:s.erase(s.end())】

然后,也有很多人使用map来记录每一个数的cnt,然后 模拟multiset【我不会啦。。】

  ③总结:学习到   #define iter(c)   __typeof((c).begin())   、 函数传参传vector等stl容器时,是按值传递的,不是传地址,

也就是说,修改传进来的vector不会改变原来的vector

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#define re register
#define ll long long
#define fi first
#define se second
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
const int maxn = 1e5+11;
int n,b;
vector<int>v;
vector<pair<int,int> >vp;
void solve()
{
    multiset<int>s;
    v.clear();
    bool flag = 1;
    cin>>n;n*=2;
    for(int i = 0;i < n;i++)
    cin>>b,v.emplace_back(b);
    sort(v.begin(),v.end());

    for(int i = 0;flag&&i<v.size()-1;i++)
    {
        s.clear();vp.clear();
        for(int j = 0;j < v.size()-1;j++)
        if(j!=i)s.insert(v[j]);
        int pre = v[n-1];
        while(s.size())
        {
            auto it = s.end();
            it--;//必须--,因为s.end()是指向外面的外界
            int bk = *it;
            int want = pre - bk;
            s.erase(it);//每erase一次,s.end()向前移一位
            //s.erase(s.end());
            if(s.count(want))
            {
                s.erase(s.find(want));
                vp.emplace_back(make_pair(bk,want));
                pre = max(bk,want);
            }else break;
        }
        if(s.empty())
        {
            cout<<"YES"<<endl;
            cout<<v[n-1]+v[i]<<endl;
            cout<<v[n-1]<<" "<<v[i]<<endl;
            for(int i = 0;i < vp.size();i++)
                cout<<vp[i].fi<<" "<<vp[i].se<<endl;
            flag = 0;
            break;
        }
    }
    if(flag)
        cout<<"NO"<<endl;
}

int main()
{
    /*multiset<int>s;
    for(int i = 1;i <= 10;i++)
    s.insert(i);
    for(auto it=s.begin();it!=s.end();it++)
        cout<<*it<<" ";
    cout<<*s.end()<<endl;//10  s.end() 指向的元素值等于最后一个元素
    //但是erase千万不能s.erase(s.end()) 因为s.end()并不在s的范围之内
    auto it = s.end();
    cout<<" s.end()  = "<<*it<<endl;//10
    --it;
    cout<<" s.end()  = "<<*it<<endl;//10
    --it;
    cout<<" s.end()  = "<<*it<<endl;//9
    --it;
    cout<<" s.end()  = "<<*it<<endl;//8*/
    
    int t;ios;
    cin>>t;
    while(t--)
    solve();
}
View Code

 

   题目三 :前后缀+构造+思维题目 ,【考的时候确实不会,说起来容易做起来难(哭)】, 首先要会普通的抵消石子的游戏,然后再一个一个点进行枚举看看更换之后是否能够成功(最朴素的做法)。加入前后缀记录优化,可以大幅降低运行时间。

  首先来看最普通的抵消石子:我们知道最后 能成功抵消的就只有一种情况,就是 a1  < a2  >  a这样的局面,此时 

若 a1 + a3 == a2 那么就是YES 。 所以我们可以开一个 suffix数组,一个 prefix数组,分别记录如果不需要转移的时候抵消到 i 所剩下的石子。

  ① s[ i ]表示从最后面(第n个)开始抵消到 i 时,i还剩下的石子数量

  ② p[ i ]表示从第一个开始抵消到 i 时还剩下的石子数量

  ③ 如果存在 p[ i ] == s[ i + 1 ] ,那么就说明不需要转移(superpower)就可以完成。  (可以模拟一下)

  再来看加上superpower的情况:因为我们转移只是影响到 第i位 和 第i+1位,其它石子堆的抵消方式并不会产生改变,我们就特判一下就好了【不要忘了判断 a 与 s,p 大小时加上等号,比如数据  : 2 2 1 8 9 7 7 】

    

 

  题目四  : 这道题也是前后缀,但是用线段树维护最大最小值也能A(尬) 其实两者的思路是一样的,把字符串化为直角坐标系的一条曲线,然后断开询问区间(L,R)之后,将 R 右边的曲线并到 L 左边的断口处 ,所以我们需要维护前区间最大最小值,然后维护后区间最大最小值,然后后区间最大最小值还要加上 L ,R 两个断口的在y轴上的差距。

  维护区间最值显然是线段树的特长,但是使用(后缀+前缀和)来维护最值我还是第一次见。。

   CF题解各变量的含义:sur后缀区间相对最大值,sul后缀区间相对最小值,pr前缀和,prr前缀最大值,prl前缀最小值

 

 

 

   题目五 :D. Yet Another Subarray Problem  动态规划,定义dp[ i ][ j ]为以 i 结尾时,i 是所选区间的第 j 个数,如果是第一个数就要减 k ,如果不是就考虑继承。

 

 

 

 

   题目六 :思维题 / 前后缀

  解法一: 我们维护①左、右两个指针(x,y),再②维护指针上下左右所能到达的最远距离(maxl,maxr,maxup,maxdown),最后再③维护指针到上下左右界的最远距离【bestl,bestr,bestdown,bestup】(就是指针在移动过程中所能达到离maxl,maxr,maxdown,maxup的最远距离,这个最远距离的最大一个就是矩阵的宽 和 高)。

  bestl = max( abs(maxl - x) )     bestr = max(abs(maxr - x))

  bestup = max( abs(maxup - y ))  bestdown = max(abs(maxdown - y ))

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#define re register
#define ll long long
#define fi first
#define se second
#define debug(a) cout<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
using namespace std;
ll maxl,maxr,maxup,maxdown;
ll bestr,bestl,bestup,bestdown;
string s;
int main()
{
    ios;
    int t;
    cin>>t;
    while(t--)
    {
        cin>>s;
        ll len = s.size();
        maxl = maxr = maxup = maxdown = 0;
        bestl = bestr = bestup = bestdown = 0;
        ll x = 0,y = 0,x1 = 0,y1 = 0;
        for(int i =0;i <len;i++)
        {
            if(s[i]=='W')y++;
            else if(s[i]=='S')y--;
            if(s[i]=='A')x--;
            else if(s[i]=='D')x++;
            maxr = max(maxr,x);
            maxl = min(maxl,x);
            maxup = max(maxup,y);
            maxdown = min(maxdown,y);

            bestl = max(abs(x-maxl),bestl);
            bestr = max(abs(x-maxr),bestr);
            bestup = max(abs(y-maxup),bestup);
            bestdown = max(abs(y-maxdown),bestdown);
        }
        ll n = maxup-maxdown+1,m = maxr-maxl+1;
        //debug(n),debug(m);
        if(bestl!=bestr&&bestup!=bestdown&&n>2&&m>2)
            cout<<((m>n)?(n-1)*m:(m-1)*n)<<endl;
        else if(bestl!=bestr&&m>2)
            cout<<(m-1)*n<<endl;
        else if(bestup!=bestdown&&n>2)
            cout<<(n-1)*m<<endl;
        else cout<<n*m<<endl;
    }
}
View Code
解法二(前后缀思路):【一位神牛的讲解(转)】 
 这个字符不是随便可以加的,因为会出现,你加了一个字符
使左边的最小值往右边移了一下,但是导致最右边往右移了,得不偿失
 首先我们考虑垂直方向,我们假设它在0这个位置,那么他所移动的
位置就是差值为1的点,我们在一个位置前面加了W,那么他后面所有点的坐标都加一
如果你在一个位置前加了s,那么他后面的所有点的坐标会减一,
你要保证可以减小,那么你加一的时候,后面不能有最大值,并且前面不能有最小值
减一的时候后面的不能有最小值,并且前面不能有最大值,否则都不可以。
 所以要找那种,可以加的情况,就是加了可以减少,不会增加也不会不变的那种
因为垂直和水平情况一样,我们只考虑一边
我们必须明确你要加一,那么后面的全部加一,你要减一那么后面的全部减一
垂直
假如它达到了很多次最大值和很多次最小值
1、当最后一个最大值在第一个最小值前面的时候,我们只要将后面的值加一,最小值减少
而你改其他的位置,要么会使值变大,要么会使值不变
2、当最后一次最小值出现在第一个最大值之前时,只要将最小值后面的减一,那么值也会变小
其他两种情况改了和没改一样,或者造成变大

   题目七 : D. Extreme Subtraction 思维 / 差分 / 贪心(易理解的贪心):

  这里讲一下差分的做法:【记住,差分数组一般用于区间加减操作】

  原数组: a0  a1  a2  a3 ................ an 

 差分数组:d0  d1  d2  d3  .................dn    dn+1【多一项】

 其中 d0 = a0  , d1 = a1 - a0  , d2 = a2 - a1 ,d3 = a3 - a2  ............................ , dn+1 = - an

  根据差分数组的性质,在差分数组两个端点加减既可以完成在原数组加减的任务

  那么我们就只需要将 d中小于0的加一,同时 d1 - 1, d中大于0的减一 ,同时dn+1 + 1

  如 1  10  9  10  10  ---> 差分数组  : 1  9  -1  1  0  -10 【分别从  d1  到 dn+1】  1 和 -1 抵消,-10 和 9+1抵消,所以这个数列是YES的

  题目又说左右两个端点【即a1,an可以自减,所以就不用管了】,我们只需要将 d(2~n)化为0,然后判断以下左右两个端点是否大于等于0就可以了。【综上,这道题不是差分板子题我直播吃掉电脑屏幕】

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e4+7;
int n,m,p;
 
int a[maxn],b[maxn];
int main(){
    int t,T=0;
    cin>>t;
    while(t--)
    {
        ll sum=0;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            b[i]=a[i]-a[i-1];
        }
        for(int i=2;i<=n;i++)
       {
            if(b[i]<0) sum-=b[i];
        }
        if(a[1]>=sum) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
 
    return 0;
}
    
V使用d【1】来消去各项
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef long long ll;
int qsm(int a,int b,int c)
{
    int res=1;
    while(b)
    {
        if(b&1)
        res=res*a%c;
        b>>=1;
        a=a*a%c;
    }
    return res;
}
const int N=30010;
int a[N];
int d[N];
int n;
bool isok()
{
    int res=0;
    for(int i=2;i<=n;i++)
    if(d[i]>0)res+=d[i];
    return d[n+1]+res<=0;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {

        cin>>n;
        for(int i=1;i<=n;i++)
        cin>>a[i],d[i]=a[i]-a[i-1];
        d[n+1] = -a[n];
        if(isok())
        cout<<"YES"<<endl;
        else
        cout<<"NO"<<endl;
    }
}
V使用d【n+1】来消去各项

 

 

   题目八 : 【逆康托展开】数学题,快速求出 n 个数的第 k 个全排列!!!

【摆脱循环k次next_permutation就在现在!!】

  第一步:我们需要一个数组fac预处理出1~n的阶乘。

  第二步:调用kth_permutation函数.....这么简单??对啊,诶,你别走啊,我板子还没给你。

先来看看原理:

 ①n个数的序列一共有n!种排列方法  

 ②我们先确定排在第一位的数字,那么n个数的序列,就降级为n-1个数的序列

 ③循环第二步,直到所有数按位排排坐好。

模拟过程:【更详细请百度 康托编码 / 全排列生成模板】

 1 2 3 4 5 的第64个排列:

 ①由定理一(因为后四位数字全排列有24种)知第1~24个排列时,1排在第一位,第25~48,2排在第一位,第49~72,3排在第一位【后面以此类推+24】

 ②64处于49~72之间,所以我们取出3,把他放在答案序列第一位。 

 ③现在剩下4个数字,所以问题转换为求这四个数的第(64-48=16)个排列。

 ④(后三位数的全排列有6种),1~6是1在第一位,7~12是2在第一位,13~18是3在第一位【由于3取了出来,4代替3】

 ⑤现在答案序列就是 3 4 ,剩下1 2 5没归队,我们要求第(16-12=4)个排列。

 ⑥(后两位数全排列有2种)1~2是1第一位 ,3~4是2第一位,所以取2,答案序列为 3 4 2

 ⑦(剩下两个数 全排列为 1 5 和 5 1)我们要选第(6-4=2)个,即 5 1 

 ⑧所以答案是 3 4 2 5 1

//n个数,第k个排列,下标从0开始
//k--重要一步(不知道,反正没了不行
int fac[25];
vector<int> kth_permutation(int n,long long k)
{
    /*建议放在函数外面预处理
    fac[0] = 1;
    for(int i = 1;i <= n;i++)
        fac[i] = fac[i-1]*i;
        */
    vector<int>v,ans;
    for(int i = 1;i <= n;i++)v.push_back(i);
    if(k==0)return v;
    k--;
    for(int i = n;i > 0;i--)
    {
        int base = fac[i-1];
        int tok = k/base;
        k %= base;
        ans.push_back(v[tok]);
        v.erase(v.begin()+tok);
    }
    return ans;
}
View Code

 

  HDU1496 : Hash练习题。

  判断等号两边是否相等。我们将等式化为 x1 x2 在左 ,x3 x4在右,然后枚举n^2,并且使用两个数组记录以下数值所到达的地方【记住,能到达的值是+1,而不是=1】,然后我们在枚举的过程中统计答案就好。

  注意特判:由于数据原因,特判减去很多时间【但是不管数据 怎么样,正负两方的特判都是我们理所当然应当想到的】

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#pragma GCC optimize(3)
#define re register
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cout<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
const int maxn = 1e6+1111;
int pos[maxn],neg[maxn];
int n,m,a,b,c,d;
int ans = 0;

int main()
{
    while(~scanf("%d%d%d%d",&a,&b,&c,&d))
    {
        //特判正负的情况,直接从2s降到200ms
        if(a>0&&b>0&&c>0&&d>0||a<0&&b<0&&c<0&&d<0)puts("0");
        else
        {
            me(neg,0),me(pos,0);ans = 0;

            for(int i = 1;i <= 100;i++)
            for(int j = 1;j <= 100;j++)
            {
                int x = i*i*a+j*j*b;
                if(x>=0)pos[x]++;//别忘了等于0的情况
                else neg[-x]++;
            }

            for(int i = 1;i <= 100;i++)
            for(int j = 1;j <= 100;j++)
            {
                int x = i*i*c+j*j*d;
                if(x>0)ans+=neg[x];
                else ans+=pos[-x];
            }

            printf("%d\n",ans*16);
        }
     }
}
View Code

 

  Nowcoder11038/D :一道本来 很简单但是被我想得很复杂的题目 。题目要求【在走最少的路的前提下,那么走最少,也就是各个节点只需要联通就 可以了,所以直接最小生成树。。。】我一直在想,如果没有了这个最少的路的条件,题目 应该怎么做呢?

 

 Nowcoder11038/C:难题【思维+容斥+组合】

我想到了容斥,但是我不是从小到大来计算的【所以挂了】。之所以从小到大,是因为大的不会对小的产生影响,而小的影响大的。

我们先将a数组小到大排序,考虑怎么计算:

对于第i只猫咪,它的名字还剩下 \( 26^{1} + 26^{2} + ... + 26^{a_{i}} - (i - 1) \)  种选择,所以直接乘进去答案即可

关于这个程序有一个明显的bug,因为sum数组是取模的,不能直接和 \( i - 1 \)判大小,而是应该定义一个struct表示是否已经大于mod才行

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#pragma GCC optimize(3)
#define re register
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cout<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
const int maxn = 1111,mod = 77797;
const double eps = 1e-4;
ll p[111],sum[111],a[maxn],n,m,ans;
int main()
{
    p[0] = 1;
    for(int i = 1;i <= 11;i++)p[i]=p[i-1]*26%mod;
    for(int i = 1;i <= 11;i++)sum[i]=(sum[i-1]+p[i])%mod;
    while(~scanf("%lld",&n))
    {
        bool f = 1;
        for(int i = 1;i <= n;i++)
            scanf("%lld",&a[i]);
        ans = 1;
        sort(a+1,a+1+n);
        for(int i = 1;i <= n;i++)
        {
            if(sum[a[i]]-i+1<=0)
            {
                cout<<-1<<endl;
                f = 0;break;
            }
            ans = (ans*(sum[a[i]]-i+1))%mod;
        }
        if(f)cout<<ans<<endl;
    }
}
View Code

 

 题目十二:【二分判断答案】给你一个长为n的数组,两个整数n,m,将这个数组分为连续的m份,请你选一种分法,使得这m份之中(所有数的和)的最大值最小。

  这道题我们可以预处理出数组的【所有数的和】和【数组最大的数】,然后 二分范围就是【maxx~sum】

  我们选一个数 mid 作为最大的【sum】中的最小值,然后判断能否分成m份,如果不能,就让 mid 小一点,否则,就让 mid 大一点。最后输出 mid 。

  总结:一般难以想到解决方法的题目,我们可以试着判断答案是否具有单调性【这句话:如果不能,就让 mid 小一点,否则,就让 mid 大一点。如果问题存在一个界限,那么我们就试一下去二分答案。】平时多留一个心眼想想题目,多做多练。

 

CF div2 CONTEST  题目十三~十五:

  第一题:

  Hills and valleys : 【暴力模拟+枚举】一道非常简单的题目,竟然又无从下手 。。一个十分简单的思路,我们将 n 个值都修改一遍,然后跑一遍O(n)  寻找最小值答案,但是这样做法是 O(n方) 。 我知道每个值要么改到与左边一样,要么改到与右边一样,就能消掉这个 山谷/山峰  ,然后顺势左右两个 山谷/山峰 甚至也能消掉。所以预处理使用 is数组记录之前是不是山峰/山谷,之后直接改变 第i位的值 ,就再调用ishiil/isvalley函数判断一下,取差值为 d ,答案为 : ans = min(ans , ans + d ).

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#pragma GCC optimize(3)
#define re register
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cout<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
inline ll read(){
   ll 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 = 3e5+11,inf = 0x3f3f3f3f3f3f,mod=1e9+7;
int a[maxn],n,k,m,tot,ans,is[maxn];
int isone(int x)
{
    if(x>1&&x<n&&a[x-1]<a[x]&&a[x]>a[x+1])return 1;
    if(x>1&&x<n&&a[x-1]>a[x]&&a[x]<a[x+1])return 1;
    return 0;
}

int main()
{
    int t,x,v;
    t = read();
    while(t--)
    {
        n = read();
        ans = inf, tot = 0;
        me(is,0);
        for(int i = 1;i <= n;i++)
            a[i] = read();
        for(int i = 1;i <= n;i++)
            if(isone(i))tot++,is[i]++;
        for(int i = 2;i < n;i++)
        {
            int temp = a[i];
            a[i] = max(a[i-1],a[i+1]);
            ans = min(ans,tot-is[i]-is[i-1]-is[i+1]+
                        isone(i-1)+isone(i)+isone(i+1));
            a[i] = min(a[i-1],a[i+1]);
            ans = min(ans,tot-is[i]-is[i-1]-is[i+1]+
                        isone(i-1)+isone(i)+isone(i+1));
            a[i] = temp;
        }
        printf("%d\n",(ans==inf?0:ans));
    }
}


//10 1 2 1 2 1 2 1 2 1 2







 
View Code

  第二题:

  Three bags: 【思维题】又是我没想到的题目

  题解

  只进行奇数次操作的数是负数,进行偶数次是正数

  我们将只进行一次操作的数称为【桥】,可以知道,【桥】的值最后是负数的。

  第一种情况:我们把两个最小值当作【桥】【这两个最小值一定 要在不同背包】

  做法:我们只需要取三个背包中选取每个背包中最小的数出来再对比一次,取出三个最小值中最小的两个,再求和 : mn = min1 + min2 + min3 - max(min1,max(min2,min3)) 。

  第二种情况:我们取一个背包里面所有的数当作【桥】

  做法:求出三个背包的 sum 值,取最小的那个。

 这样做的原因是,如果一个背包的数全是1,有100个,但是剩下两个背包中只有一个数,都是1e9。那么我们就要减掉第一个背包中的所有数。 

  ans = sum1 + sum2 + sum3 - min( mn , minsum )*2; 乘二是因为sum1+sum2+sum3里面重复里,所以要多减一次

   第三题:

  sum of paths动态规划。虽然很多人说简单。。。【屁】

  第一步:我们先要用 DP 求出【走 i 步后停在 第 j 个点的 种数】

  dp[ i ][ j ] = dp[ i-1 ][ j-1 ] + dp[ i-1 ][ j+1 ]  

  第二步:如果走 i 步后以 j 为终点,那么我们从 j 出发,也可以走 i 步回去,所以【走 i 步后停在 第 j 个点的 种数】等于 【 从第 j 个点出发 走 i 步的种数】

  第三步:我们上面那样求,其实是默认第 i 个点是必走的,根据组合数原理,选一个起点和一个终点走k步【可重合】,就是 cnt[ i ] = dp[ k-j ][ i ] * dp[ j ][ i ] 。 

  之后就是 a[ i ]*cnt[ i ] 累加起来就是答案了。

  每一次询问只是更新数值,并没有改变经过 该点 的次数,所以O(1)询问回答就可以了

  总复杂度:O(n^2) + O(q)

 

CF div2 CONTEST  题目十六~十七:【CF ~ div2】

  第一题:

  BFS染色求最小点覆盖 :【好题!!】定义老师为【主节点】,【主节点】要覆盖所有的主要边,即最小生成树上的边,而且所有的主要点不能相邻。【这应该是一个最小点覆盖的问题吧。】

  特别留意的是,这道题的染色不能使用类似 DFS 的染色法【即染一个点,就 访问一个点,这样无法保证【主节点】不相邻,就算发现了两个主节点相邻,也改不了 。】

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#pragma GCC optimize(3)
#define re register
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cout<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
inline ll read(){
   ll 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 = 3e5+11,inf = 0x3f3f3f3f3f3f,mod=1e9+7;
vector<int>e[maxn],ans;
int cnt,n,m,a,b,c,col[maxn];
bool vis[maxn];
bool bfs(int x)
{
    //sum变量记录访问过的节点数量 ,以此判断一个图是不是n个节点
    //cnt记录col为1的节点数量
    int sum = 1;
    queue<int>q;
    q.push(x);
    cnt += col[x] = 1;
    vis[x] = 1;
    while(!q.empty())
    {
        x = q.front();
        q.pop();
        for(int v:e[x])
        {
            if(col[v]==-1)
                cnt += col[v] = col[x]^1;
            //加上一个col==1的节点,如果col==0,就cnt+=0
            else if(col[v]==1&&col[x]==1)
                col[v] = 0,cnt--;
            //删掉一个col==1的节点,cnt--
        }
        for(int v:e[x])
            if(!vis[v])vis[v]=1,sum++,q.push(v);
    }
    return sum == n;
}

int main()
{
    int t = 1,kase = 1;
    ~scanf("%d",&t);
    while(t--)
    {
        n = read(),m = read();
        for(int i = 0;i <= n;i++)
            col[i]=-1,vis[i]=0,e[i].clear();
        cnt = 0;
        for(int i = 1;i <= m;i++)
        {
            a=read(),b=read();
            e[a].emp(b),e[b].emp(a);
        }
        if(bfs(1))
        {
            printf("YES\n%d\n",cnt);
            for(int i = 1;i <= n;i++)
            if(col[i]==1)printf("%d ",i);
            puts("");
        }else puts("NO");
    }
}
View Code

   

  第二题:

  hash灵活运用于记录特征值 :本题题目我都看懵了。。【直到后来发现,product:乘积。。。】

  贴两篇我觉得不错的题解: FIRST  SECOND 

  hash特征值在于如何设计一个hash函数使得具有相同特征值的  元素  的 hash值相等,但是又能够不出现hash冲突。这里选择预处理两个power数组,记录base1,base2的幂【双hash】,然后将质因子的下标(index)当成幂 。

 

 

 CF div3 CONTEST  题目十八~二十:

  第一题:

  枚举 与 二分 结合【其实我说,这道题是前缀和优化枚而已】 :【好题!!】涉及到最值得选择,第一反应是贪心,我们如果把所有物品加起来,然后再选价值小的,慢慢减,这可以吗?当然是不可以,因为涉及到两堆物品,我们无法通过贪心完全除去影响【这道题有太多特殊情况,特别是加一个大数和加一个小数在不同时刻优劣性不同。】

  涉及到多情况问题,枚举是一个不错的方法,但是数据量这么大如何枚举?关注到【享受值为1的物品取走一个都是减一,享受值为2的就减2,所以我们要先从占内存大的开始取【这里就是前缀和优化啦】。所以先sort一遍,然后用lowerbound找一个合适的值就可以了,但是注意是从 0 开始。】

 int pos = lowerbound(s+1,s+1+n,want) - s;

 【然后lowerbound查找时,如果数组内最大的数组都没有这个want变量大,那么就会返回n+1,这个要牢记阿!!】

#include <stdio.h>
#include <iostream>
#include <ctime>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#pragma GCC optimize(3)
#define re register
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cerr<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
template<typename T>inline int read(T&res){
    ll x=0,f=1,flag=0;char ch;
    flag=ch=getchar();
    if(flag==-1)return -1;
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        flag=ch=getchar();
        if(flag==-1)return -1;
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        flag=ch=getchar();
        if(flag==-1)return -1;
    }
    res = x*f;return flag;
}
template<typename T,typename...Args>
inline int read(T&t,Args&...a){
    int res;
    res=read(t);if(res==-1)return -1;
    res=read(a...);return res;
}

const int maxn = 3e5+11,inf = 0x3f3f3f3f,mod=998244353;
ll n,m,a[maxn],b,fir[maxn],sec[maxn];
void solve()
{
    scanf("%lld%lld",&n,&m);
    ll sum = 0,ans = inf,top1=0,top2=0;
    for(int i = 1;i <= n;i++)
        read(a[i]),sum+=a[i];
    for(int i = 1;i <= n;i++)
        read(b),(b==1?fir[++top1]=a[i]:sec[++top2]=a[i]);
    sort(fir+1,fir+top1+1,greater<ll>());
    sort(sec+1,sec+1+top2,greater<ll>());
    if(sum<m)puts("-1");
    else
    {
        sum = 0;
        for(int i = 1;i <= top1;i++)
        fir[i]+=fir[i-1];
        for(int i = 1;i <= top2;i++)
        sec[i]+=sec[i-1];
        //二分要从0开始,因为可以不选
        for(int i = 0;i <= top1;i++)
        {
            sum = fir[i];
//sec不用+1
            int pos=lower_bound(sec,sec+1+top2,(m-sum>0?m-sum:0))-sec;
            if(pos<=top2&&sum+sec[pos]>=m)
                ans=min(ans,i+pos*2);
        }
        printf("%lld\n",(ans==inf?0:ans));
    }
}


int main()
{
//这些ifdef要在main函数里面
#ifndef ONLINE_JUDGE
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    ll sta = clock();
#endif


    int t = 1,kase = 1;
    ~scanf("%d",&t);
    while(t--)
        solve();



#ifndef ONLINE_JUDGE
    ll en = clock();
    cerr<<"运行时间"<<(double)(en-sta)<<"ms"<<endl;
#endif
}
View Code

  

  第二题:

  思维题 思维题2  -----> 第一题有点像塞瓦韦斯特定理

对第一题而言,2021比2020多了1,要判断一个正数是否可以由2020和2021表示 :x = 2021a + 2020b --->   x = 2020 * (a+b)+a ,得出a<a+b,所以我们判断1的个数是不是比2020的数量少就可以了。

对第二题来说,每一个奇数都是YES【自己除自己】,那么偶数呢?偶数都能表示成  2*n -> 2*2*m --> 2*2*2*k 等等形式,总之一定就是有一个2就可以了,如果存在一个奇数divisor,那么n / m / k 中肯定是存在一个奇数分解因子的,所以,我们将所有的前缀 2 都除掉,最后检查一下 n 是不是奇数就可以了。

  来几道类似的数学思维题吧,免得好像连小学数学都不会。。

  练习题1   练习题2  练习3【同余最短路/扩展欧几里德】   光棍数【1的个数为偶数都可以由11整除,1的个数为3的倍数,能被三整除】

  

   第三题【构造套路题】

   矩阵异或 :进行行异或 / 列异或,看看是否最后第一个矩阵能和第二个矩阵一模一样。首先,我们只需要进行第一行元素德列异或,使得第一行元素相同,因为第一行元素已经相同,后面几行都不能进行列异或了,只能进行行异或,那么只能进行行异或【即整行取反,所以只需要判断这样行和  “ 目标矩阵 ” 的是不是一模一样 或者 完全相反即可,如果不是,就NO,检查到最后,输出YES(使用cnt数组记录第一行该列是否进行过列异或)(本来想拿二进制做的,但是数据1000了,不知道bitset行不行)】

#include <stdio.h>
#include <iostream>
#include <ctime>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#pragma GCC optimize(3)
#define re register
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define de(a) cerr<<#a<<" = "<<a<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#pragma GCC optimize(3)
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
inline ll max(ll a,ll b){return a>b?a:b;}
inline ll min(ll a,ll b){return a>b?b:a;}
template<typename T>inline int read(T&res){
    ll x=0,f=1,flag=0;char ch;
    flag=ch=getchar();
    if(flag==-1)return -1;
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        flag=ch=getchar();
        if(flag==-1)return -1;
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        flag=ch=getchar();
        if(flag==-1)return -1;
    }
    res = x*f;return flag;
}
template<typename T,typename...Args>
inline int read(T&t,Args&...a){
    int res;
    res=read(t);if(res==-1)return -1;
    res=read(a...);return res;
}

const int maxn = 1011,inf = 0x3f3f3f3f,mod=998244353;

bool a[maxn][maxn],b[maxn][maxn];
int n,m,ans,flag,cnt[maxn];
char str[maxn];

void solve()
{
    read(n);
    flag = 0;
    for(int i = 1;i <= n;i++)
    {
        gets(str+1);
        cnt[i]=0;
        for(int j = 1;j <= n;j++)
        {
            a[i][j]=(str[j]=='0'?0:1);
        }
    }
    getchar();
    for(int i = 1;i <= n;i++)
    {
        gets(str+1);
        for(int j = 1;j <= n;j++)
        {
            b[i][j]=(str[j]=='0'?0:1);
            b[i][j]^=a[i][j];
        }
    }

    for(int i = 1;i < n;i++)
    if(b[1][i]^cnt[i]!=b[1][i+1]^cnt[i+1])cnt[i+1]^=1;
    for(int i = 2;i <= n;i++)
    for(int j = 1;j < n;j++)
    {
        if(b[i][j]^cnt[j]!=b[i][j+1]^cnt[j+1])
        {
            puts("NO");
            return ;
        }
    }
    puts("YES");
}





int main()
{
//这些ifdef要在main函数里面
#ifndef ONLINE_JUDGE
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    ll sta = clock();
#endif


    int t = 1,kase = 1;
    ~scanf("%d",&t);
    while(t--)
        solve();



#ifndef ONLINE_JUDGE
    ll en = clock();
    cerr<<"运行时间"<<(double)(en-sta)<<"ms"<<endl;
#endif
View Code

 

 CF  div2 CONTEST 题目二十一~二十二

  这一次比赛的题目有较多的数学题,又感觉有点吃力了。

  第一题:

  ROW GCD :考虑一个性质:gcd( x , y ) == gcd( y , x - y ) == gcd( x-y , 2*y - x ) 【辗转相减】

  思路来源:Nowcoder的题目:小阳的贝壳 通过用线段树维护差分数组,来求各个区间的GCD。

  那么这一题也是一样,通过辗转相减,我们发现可以消掉 bj  请看:a1+bj ,a2-a1,a3-a2,a4-a3.......以此类推,如果我们预处理出差分数组的GCD,那么一切可以O(n)解决了。

  

  第二题:【我觉得这道B题比C题难是怎么了(哭)】

  Move and Turn :首先第一眼,DFS题,写了交上去,嗯,T了。。。。后来就没后来了。

题解:如果n是偶数,最远可以到达 n/2  处,若有一次向左改成向右,那么就会变成 n/2 - 2,以此类推,最后会先经过 x = 0,直到 x = -n/2 . 规律开始出来了。n为偶数时,对于横竖都一样,然后  x , y  的数量 就是 n/2  + 1

     如果n是奇数,竖方向走 n/2 + 1 或 n/2 步 ,同样,横方向也是。然后数量是:n/2 + 1或 n/2 + 2. 由于有两种可能,最后要乘2.

  n&1时 : ans = 2*(n/2 + 1)*(n/2 + 2)

  else :       ans = (n/2 + 1)*(n/2 + 1) 

至于为什么乘起来,因为组合数学【竖方向选一个点,横方向选一个点】。

 

 

 CF散题练习:二十三~二十六

 ①E. Infinite Inversions 很好的一道题,难点在于1.需要计算逆元 2.数据范围十分之大【1~1e9】

但是也是有解决的办法的:

 首先看到要计算逆元【想到树状数组计算逆元的板子】思路如下:
 1.第一步先将所有的数都利用树状数组记录前缀和【即add()操作】

 2.第二步从左到右遍历数组,经过一个数 a(为方便简称为a),先add(i,-1)操作删去自己,就利用树状数组求 1 ~ a 的前缀和【减掉这个数自己】,那么这个前缀和的含义就是:【在a之后出现,而且比a小的数字的数量之和,因为凡是在前面出现过的数字,我们都用add操作删掉了,那么剩下的就是存在与a相互组合的逆序对】

 就这样累加即可。由于本题使用到离散化,树状数组求逆序数的方法得到了很好的运用【不然平时很难有机会用阿】

  其次看到数据范围,那么大,想到了和扫描线相关的算法【即离散化】,我们发现在求逆序数时,有许多点并不需要移动,所以直接缩掉,用一个新的编号代替。【这个方法实在是太妙了。】

const int maxn = 5e5+11,mod=1e9+9;
ll tr[maxn<<2],f[maxn];
ll n,m,k,top;
ll a[maxn],b[maxn],d[maxn];

struct ST
{
    ll i,v;
    ST(){i=v=0;}
    bool operator<(const ST&st)const
    {
        return v < st.v;
    }
}s[maxn];

inline void add(ll id,ll val)
{
    while(id <= top && id > 0)
    {
        tr[id]+=val;
        id += lowbit(id);
    }
}

inline ll sum(ll id)
{
    ll sum = 0;
    while(id)
    {
        sum += tr[id];
        id -= lowbit(id);
    }return sum;
}

inline void solve()
{
    read(n);
    for(int i = 1;i <= n*2;i++)
    {
        read(s[i].v);
        s[i].i = i;
    }
    sort(s+1,s+1+2*n);

    //离散化
    top = 0;
    for(int i = 1;i <= n*2;i++)
    {
        if(s[i].v > s[i-1].v)
        {
            if(s[i].v - s[i-1].v > 1)
                f[++top] = s[i].v - s[i-1].v - 1;
            f[++top] = 1;
        }
        if(s[i].i&1)
            a[s[i].i/2] = top;
        else
            b[(s[i].i-1)/2] = top;
    }

    //初始化数组【缩点之后】
    for(int i = 1;i <= top;i++)
        d[i] = i,
                add(d[i],f[i]); // 提前初始化所有的前缀和

    //转换
    for(int i = 0;i < n;i++)
        swap(d[a[i]],d[b[i]]);

    //树状数组计算逆序数
    ll ans = 0,tot = 0,tem;
    for(int i = 1;i <= top;i++)
    {
        add(d[i],-f[d[i]]);    //减掉自己
        ans += sum(d[i])*f[d[i]];
    }
    printf("%lld\n",ans);
}    
View Code

 

C. Searching Local Minimum :为什么那么多人说这道题水呢??明明这个二分模型就很难想到。。虽然把,看到题解之后,发现还是很有道理的。

 第一点:如果一个区间 [ L,R ]上,有a[ L ] > a[ L+1 ]  and  a[ R ] < a[ R+1 ],那么这个区间一定存在local minimal value。

 第二点:看完第一点之后,就可以二分了,只不过这一次二分,咱们不仅仅需要知道 a[ mid ],我们还要知道 a[ mid + 1 ],这样,我们才知道这个mid点是转移到 L 呢,还是转移到 R 。【主要是二分运用太刻板了,只会想query一个mid点,导致做不出】 

D1. Painting the Array I : 感觉有点类似贪心题目

贪心的题目一般都会有一种神奇的感觉,有点借助于直觉,又有点借助于经验。如果一道题每个点的决策对前/后的影响范围很小【比如此题 ai 的去向只由 ai+1 和 两个序列的末尾数字 决定范围就很小 】或者这也是DP的思维?

D2. Painting the Array II : 求最小值而已,做法与上面一样【思维要逆过来】

 

 CF散题练习二十七~三十

① 数论练习题:除法分块 Floor and Mod

 推理过程涉及到模除,比较容易,但是写除法分块却老是写不对。。没办法了,以后写除法分块一定要好好处理边界问题。

比如这一题,暴力法:ans += min(floor(x/(i+1)),i-1), 拆开min之后,将x/(i+1)进行分块,1~sqrt(x+1)是i-1的贡献的,

所以我们只需要处理 sqrt(x+1)+1~y 之间的贡献,【本来我是枚举系数k:1<k*(i+1)<=x 来分块的,因为一段分块区间的贡献只能是k,然后边界问题爆炸了。。后来换了写法,改成枚举(i+1),然后用nxt计算下一个位置,nxt = min(x/k-1,y):记住,一定要k-1!!!】

const int maxn = 2e5+11;
ll n,m,x,y,q,k,sum[maxn];


inline void solve()
{
    read(x,y);

    ll lim = sqrt(x+1),ans = 0;

    if((lim-1)*(lim+1) > x && lim > 0)lim--;

    lim = min(lim,y);
    ans = sum[lim];

    lim++;
    while(lim<=y)
    {
        int k = x/(lim+1);
        if(k==0)break;

        int nxt = min(x/k-1,y);
        ans += (nxt - lim + 1)*k;
        lim = nxt + 1;
    }

    printf("%lld\n",ans);
}

int main()
{
    for(int i = 2;i < maxn;i++)
    sum[i] = sum[i-1]+i-1;

    int t;
        cin>>t;
while(t--)solve();
}
View Code

 

②思维题:old Floppy drive :

注意点:此题需要在一个无限循环数组里面,找出第一个前缀和 大于等于 xi 的一个位置。

所以公式就是 :公式① k*S + sum[i] >= xi  【其中S是1~n的前缀和( 即 sum[n] )】,那么ans = k*n + i - 1;

解决问题的关键,在于:

(1):维护一个1~n的最大值数组  mx[1~n] ,其中mx[i]代表 1~i 之间最大的前缀和的值。【这样做的好处在于:mx具备单调性,更加方便二分寻找一个大于等于 xi 的位置。】

(2):根据一点贪心的思想,我们要使得 k*n + i 是最小的,而且 k*S + sum[i] >= x ,很容易想到 使得 k+1【因为S是定值】,或者sum[i]尽可能大,但是如果选择 k+1 ,那么对ans的贡献是 +n,很明显比 第二个选择(+1)要差【故维护mx数组,使得sum值先是最大的】

所以根据(2),我们先找到k的 最小值,公式② kmin  =  ceil { ( x - mx[n] )/S } 这样,我们确定了最小的 k。

然后,根据公式③ mx[i]min >= xi - kmin*S  根据(1),mx具有单调性,所以我们二分搜索即可。【mx[0]设置为0即可,因为x>0,即使数组a中出现了负值,对答案也不会有影响】。

const int maxn = 2e5+11;
ll n,m,S,d,mx[maxn],a,b,c;

inline void solve()
{
    read(n,m);
    S = 0;
    for(int i = 1;i <= n;i++)
    {
        read(a);
        S += a;
        mx[i] = max(mx[i-1],S);
    }
    ll ans;
    while(m--)
    {
        read(a);
        if(a<=mx[n])
        {
            ans = lower_bound(mx+1,mx+1+n,a)-mx-1;
        }
        else if(S<=0)
        {
            ans = -1;
        }
        else
        {
            ll d = a - mx[n];
            ll t = ceil(1.0*d/S);
            a -= t*S;
            ans = lower_bound(mx+1,mx+1+n,a)-mx-1+t*n;
        }
        printf("%lld ",ans);
    }
    pln;
}
View Code——————不开 LL 见祖宗

 

③曼哈顿距离:Exhibition 题目要求离所有点总距离最短的点的个数 ,如果只是求一个,那么很显然就是sort后的中位数 ,

对于奇数来说,中位数只有一个,所以ans就是1;

对于偶数来说,距离最短的点在mid和mid+1之间,所以答案就是:ans=(x[mid+1]-x[mid]+1)*(y[mid+1]-y[mid]+1)【笨笨地手玩了超级久,还好没掉分】;

 

 C2. Guessing the Greatest (hard version):二分题:本来以为直接模拟【伪二分吗?】,根据第二大的点分成两个区间,不断这样分就可以找到,但是wa了 。后来改变思路 ,也是二分,一开始先确定最大值在secpos的左边还是右边,然后二分一个长度【len=abs(firpos-secpos)】就可以了【保险起见,我用了map保存了ask的输入,防止重复询问

int n,pos;
map<PII,int>mp;

bool check(int l)
{
    int mx = max(pos+l,pos);
    int mn = min(pos+l,pos);
    int a;
    if(mp[make_pair(mn,mx)]==0)
    {
        cout<<"? "<<mn<<" "<<mx<<endl;
        cout.flush();
        cin>>a;
        mp[make_pair(mn,mx)] = a;
    }
    else a = mp[make_pair(mn,mx)];
    if(a==pos)return 1;
    return 0;
}

int Find(int S,int E,int f)
{
    int L,R,mid,ans=pos+f;
    if(f==-1)
        L=1,R=pos;
    else
        L=1,R=E-pos+1;

    while(L<R)
    {
        mid = (L+R)>>1;
        if(check(f*mid))R=mid,ans = pos+f*mid;
        else L=mid+1;
    }
    return ans;
}


inline void solve()
{
    cin>>n;
    int t,ans;
    cout<<"? "<<1<<" "<<n<<endl;
    cout.flush();
    cin>>pos;
    mp[make_pair(1,n)] = pos;
    if(pos!=n&&n!=2&&pos!=1)
    {
        cout<<"? "<<1<<" "<<pos<<endl;
        cout.flush();
        cin>>t;
    }else t = pos;
    mp[make_pair(1,pos)] = t;

    if(n==2)ans = 3-pos;
    else if(pos==t&&pos!=1)ans = Find(1,pos,-1);
    else ans = Find(pos,n,1);

    cout<<"! "<<ans<<endl;
    cout.flush();
}
View Code

 

 三十一:XOR geuss :

根据XOR的性质,如果 a 不存在 1 时,那么 a^b 之后,b上还含有1的位置会完整保留下来。

那么这道题就是这样,由于题目已经说了最大就是2^14-1,所以我们可以根据上面的做法,依次将 前7位 和 后7位 空出来,然后询问两次之后,就得到x的前七位和后七位。然后&运算一下就可以了【思维题!!】

三十二:Remainer sum :

这个很简单就想到暴力,就是从y开始,以x为公差的等差数列来取索引。【sorry - TLE】、

怎么办?我们容易想到,当 x 很大的时候,基本上 3e5/x 次也不大,但是 x 很小的时候怎么办?【毫无头绪阿】

这时候一个奇妙的做法诞生了。【分治选手走出现场!!!】

我们首先选取一个 T 作为界限,大于 T的直接暴力运算,小于T 的使用数组维护S[ i ][ j ] 表示以 i 为模数,余数为 j 的总和。

T怎么选?暴力的复杂度是O(3e5/T),数组维护是O(T),不宜偏私,使内外异法也;那么只能是T = sqrt(3e5) 了,这适合不知道数据怎么变动的情况下使用【反正O(m*√3e5) 在4秒 内不会TLE】 

三十三:number of permutation:【二元组x,y序列】

很容易想到容斥。【但是我因为细节挂了。】

①如果排序之后存在相邻之间相等,那么这一串的种类数是 : ∏A(n,n)  这明明是累乘,我为什么加了起来??傻子奥

②根据上面①的思路,转化为出现的数字的【数量cnt】的累乘,不要再排序之后暴力cnt++啦,TLE不烦吗?

③根据容斥定理,x递增时,我们累计了bad的种数,y递增时,我们又累计了一次,所以最后将x,y都排序,如果排完序之后【验证check函数】,x,y序列都是递增,那么就存在减重复的情况,所以最后要加回来【好好学容斥,哭】。

④cmp函数【比较器】里面千万不要写<= / >= 啦,写 < / >就好啦,谁知道某个傻逼因为写了个<=而RE了半天呢?

const int maxn = 6e5+11,mod = 998244353;
ll n,jie[maxn];
ll a[maxn],b[maxn];
 
struct node
{
    int a,b;
    node(){a=b=0;}
}s[maxn];
 
bool cmp(node a,node b)
{
    if(a.a==b.a)
        return a.b<b.b;
    else
        return a.a<b.a;
}
 
bool check()
{
    for(int i = 2;i <= n;i++)
        if(s[i-1].a>s[i].a||s[i-1].b>s[i].b)
            return false;
    return true;
}
 
inline void solve()
{
    read(n);
    ll ans = jie[n];
    int mx = 0;
    for(int i = 1;i <= n;i++)
    {
        read(s[i].a,s[i].b);
        a[s[i].a]++;
        b[s[i].b]++;
        mx = max(mx,s[i].a);
        mx = max(mx,s[i].b);
    }
    ll sum = 1,sum2 = 1;
    for(int i = 1;i <= mx;i++)
    {
        if(a[i]>1)
            sum = mul(sum,jie[a[i]],mod);
        if(b[i]>1)
            sum2 = mul(sum2,jie[b[i]],mod);
    }
    ans = ((ans-sum-sum2)%mod+mod)%mod;
 
    sort(s+1,s+1+n,cmp);
    if(check())
    {
        sum = 1;
        for(int i = 1,j;i <= n;i=j)
        {
            j = i;
            while(j<=n&&s[i].a==s[j].a&&s[i].b==s[j].b)
                j++;
            sum = sum*jie[j-i]%mod;
        }
        ans = (ans+sum)%mod;
    }
    printf("%lld\n",(mod+ans)%mod);
}
View Code

 

三十四:线段树sum query?

容易想到当有进位时,就会不平衡。所以直接维护一个线段树【这颗线段树的节点维护着l~r区间上,1~9个数位上都不为0的两个数的最小值和次小值,b[i][1]表示最小值,b[i][0]表示次小值。

subset的意思是随意选取的子集即可,不需要连续。【我又傻逼了,还以为一定要连起来。。。。】

还有,Push_up函数比较难写,query返回的是结构体,因为返回的途中需要结合push_up【可以理解为merge函数】来合并两个区间的最佳答案。

#include <stdio.h>
#include <iostream>
#include <ctime>
#include <iomanip>
#include <cstring>
#include <algorithm>
#include <queue>
//#include <chrono>
//#include <random>
//#include <unordered_map>
#include <math.h>
#include <assert.h>
#include <map>
#include <set>
#include <stack>
#define lowbit(x) (x&(-x))//~为按位取反再加1
#define re register
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define lld long double
#define ull unsigned long long
#define fi first
#define se second
#define pln puts("")
#define deline cerr<<"-----------------------------------------"<<endl
#define de(a)  cerr<<#a<<" = "<<a<<endl
#define de2(a,b) de(a),de(b),cerr<<"----------"<<endl
#define de3(a,b,c) de(a),de(b),de(c),cerr<<"-----------"<<endl
#define de4(a,b,c,d) de(a),de(b),de(c),de(d),cerr<<"-----------"<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
#define _sz(x) ((x).size())
#define PII pair<int,int>
#define PLL pair<ll,ll>
using namespace std;
inline ll max(ll a,ll b){return (a>b)?a:b;}
inline ll min(ll a,ll b){return (a>b)?b:a;}
template<typename T>
inline int read(T&res){ //
    ll x=0,f=1,flag=0;char ch;
    flag=ch=getchar();
    if(flag==-1)return -1;
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        flag=ch=getchar();
        if(flag==-1)return -1;
    }
    while(ch>='0'&&ch<='9'&&flag!=-1){
        x=(x<<1)+(x<<3)+(ch^48);
        flag=ch=getchar();
    }res = x*f;return flag;
}
template<typename T,typename...Args>
inline int read(T&t,Args&...a){ //
    int res;
    res=read(t);if(res==-1)return -1;
    res=read(a...);return res;
}

inline ll mul(ll a,ll b,ll p = 9223372036854775807){ //
    a %= p;
    b %= p;
    ll c = (lld)a * b / p;
    ll res = a * b - c * p;
    if (res < 0)res += p;
    else if (res >= p)res -= p;
    return res;
}

inline ll qpow(ll x,ll y,ll mod = 9223372036854775807){ //
    if(y<0)return 0;
    ll ans = 1;
    while(y>0)
    {
        if(y&1)ans = (ans*x)%mod;
        x = x*x%mod;
        y >>= 1;
    }return ans;
}
//这有三个板子read + mul + qpow


const int maxn = 2e5+11,mod = 998244353;
const ll inf = 0x7f7f7f7f7f;
int a[maxn],n,m;
struct S//0 次小,1最小
{
    ll b[10][2];
    S(){me(b,inf);}
}tr[maxn<<2];

void push_up(S&fa,S&x,S&y)
{
    for(int i = 1;i <= 9;i++){
        if(y.b[i][1] >= x.b[i][0]) {
            fa.b[i][1] = x.b[i][1];
            fa.b[i][0] = x.b[i][0];
        }
        else if(x.b[i][1] >= y.b[i][0]) {
            fa.b[i][1] = y.b[i][1];
            fa.b[i][0] = y.b[i][0];
        }
        else {
            fa.b[i][1] = min(x.b[i][1],y.b[i][1]);
            fa.b[i][0] = max(x.b[i][1],y.b[i][1]);
        }
    }
}

void build(int ro,int l,int r)
{
    if(l==r){
        ll t = a[l];
        for(int i = 1;i <= 9;i++,t/=10){
            if(t%10!=0)tr[ro].b[i][1] = a[l];
            else tr[ro].b[i][1] = inf;
            tr[ro].b[i][0] = inf;
        }
        return ;
    }
    int mid = (l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    push_up(tr[ro],tr[ls],tr[rs]);
}

void update(int ro,int l,int r,int x,int val)
{
    if(l==x&&r==x){
        ll t = val;
        for(int i = 1;i <= 9;i++,t/=10){
            if(t%10!=0)tr[ro].b[i][1] = val;
            else tr[ro].b[i][1] = inf;
        }
        return ;
    }
    int mid = (l+r)>>1;
    if(x<=mid)update(ls,l,mid,x,val);
    else update(rs,mid+1,r,x,val);
    push_up(tr[ro],tr[rs],tr[ls]);
}

S query(int ro,int l,int r,int s,int e)
{
    //de3(ro,l,r);
    if(s <= l && r <= e){
        return tr[ro];
    }
    ll mid = (l+r)>>1;
    S a,b,ans;
    if(s<=mid)a = query(ls,l,mid,s,e);
    if(mid<e)b = query(rs,mid+1,r,s,e);
    push_up(ans,a,b);
    return ans;
}

inline void solve()
{
    read(n,m);
    for(int i = 1;i <= n;i++) {
        read(a[i]);
    }
    build(1,1,n);
    int x,l,r;
    while(m--) {
        read(x,l,r);
        if(x==1) {
            update(1,1,n,l,r);
        } else {
            S ans = query(1,1,n,l,r);
            ll P = inf;
            for(int i = 1;i <= 9;i++)
            {
                P = min(P,ans.b[i][1]+ans.b[i][0]);
            }
            if(P==inf) {
                puts("-1");
                continue;
            }
            printf("%lld\n",P);
        }
    }
}

int main()
{
    //mt19937 rng(chrono::steady_clock::
        //now().time_since_epoch().count());

    int t = 1;// int t = read(t);
    //cin>>t;
    //~scanf("%d",&t);
    //getchar();
    while(t--)
        solve();

    //system("pause");
}


 
View Code

 

 三十五:牛牛的揠苗助长:【中位数+二分】

注意到所有的稻草都在按顺序增加。所以可以视为day天里面只有【day%n】的稻草是加1的,其他的不变。所以说,如果我们确定了day%n的大小【即余数的大小,就可以知道中位数,知道中位数,就可以知道所需要的次数】。所以枚举余数从0~n-1吗?

NO 。这样的复杂度是O(n^2*logn),logn是因为排序。会TLE的。

所以我们使用二分,复杂度为O(logn*logn*n)

单调性证明:假设K天能恰好能解决问题,那么K+1必然也行(可以把k+1天增长的那个用魔法减去),所以K天之后都是可以的。

 那么小于K天的,比如K-1天,有两种可能,①有一块草和中位数差2个单位②有两块草和中位数差1个单位。那么更小的必然就不行了。

 

 三十六:Fence great : 【DP】

这道题我试遍了其它的算法,都不会做,然后 最后想DP,发现前面的是会影响到后面的,然后就放弃了。。。。

不错,前面的确实会影响到后面的,但是这个影响是有限度的。手玩几个数据之后发现,不管如何,一个fence最多变化两次,【即一个fence有0、1、2三种状态,所以我们只要将一块板子分成三块板子来理解就行了】

定义:dp[ i ][ j ] 为第 i 块板子变化 j 次使得前1~i块板子相互不矛盾用的最少花费。

const int maxn = 4e5+11,mod = 998244353;
const ll inf = 0x7f7f7f7f7f7f7f7f7f;
ll n,a[maxn],b[maxn],dp[maxn][3];
 
inline void solve()
{
    read(n);
    for(int i = 1;i <= n;i++)
    {
        read(a[i],b[i]);
        dp[i][1] = dp[i][0] = dp[i][2] = inf;
    }
    for(int i = 1;i <= n;i++)
    {
        for(int j = 0;j < 3;j++)
        for(int k = 0;k < 3;k++)
        if(a[i]+k!=a[i-1]+j){
        dp[i][k]=min(dp[i][k],dp[i-1][j]+k*b[i]);
        }
    }
    ll ans = inf;
    for(int i = 0;i < 3;i++)
        ans = min(ans,dp[n][i]);
    printf("%lld\n",ans);
}
View Code

 

三十七:A-B string:【构造思维题 】

看到题目只含A-B,就觉得是思维题了。但是一直没想到。

题意:寻找一个区间,区间内的每个字符都属于一个或以上的回文串。

一个区间内的所有字符都属于回文串的话,好像确实很难想。那么反过来,如果一个区间总有那么一个字符不属于回文串,那么是怎么样的呢??

我们首先找到一个区间,里面的字符都是属于回文串的:     比如:   ******    ,我们现在在左边【或者右边】,加入一个字符A,变成  A******,若我们加进去之后他变成反例了,当且仅当区间里存在一个A。【因为区间只有A、B,我们从刚刚加进去的A开始,向右找,找到第一个A,比如说:A***A**,那么两个A必然形成回文串。】所以只有 A在最左侧【如果不在最侧边,必然不成立如:*A*、AA】,同时只有一个A时才成立。

const int maxn = 3e5+11,mod = 998244353;
const ll inf = 0x7f7f7f7f7f;
char str[maxn];
int n;
inline void solve()
{
    read(n);
    gets(str+1);
    ll ans = 1LL*n*(n-1)/2,pre=1;
    for(int i = 2,j;i <= n;i++)
    {
        if(str[i]==str[i-1])continue;
        ans++;
        ans -= i - pre;
        pre = i;
    }
    pre = n;
    for(int i = n-1,j;i > 0;i--)
    {
        if(str[i]==str[i+1])continue;
        ans -= pre - i;
        pre = i;
    }
    printf("%lld\n",ans);
}
View Code

 

 三十八:Salary Changing:【二分+最大中位数判断】

 首先,很明显就是一道二分判断的题目,关键在于怎么判断。

易错点:

①没有明确单调区间在哪里。首先,最小的中位数一定是在排序之后的S[ mid ].L,那么我们确定了最小值,最大值就是出现过的S[ ? ].R。  然后在这个区间内跑二分,如果不是,可能会出现错误,因为区间不单调。【这里要十分注意阿,不单调的区间要搞成单调后再二分:想一下可能出现的最小值与最大值即可,想不到也没办法了(哭)】

怎么判断?最大中位数,我们如果贪心的话,直接令 后面 n/2 + 1 位数都是mid,这样我们就能够使得中位数最大。但是有一种情况,就是S[ i ].L > median 的情况,这时,我们就只能减去S[ i ].L 了。

累计一个cnt记录 大于等于median的数量,如果cnt已经达到n/2+1,那么后面的值直接减去S[ i ].L 即可。

 贪心的思路:我们先根据S[ i ].L 来排序,小的在前,大的在后,明确一点,①大于median的L值必然被减去,②当cnt还没达到n/2+1时,小于median的减去median③cnt达到n/2+1了,直接减S[i].L;

这时,我们减的S[i].L是经过维护之后最小的。从而保证了Salary都加到median上面。

 

 三十九:Infinite Fence【数学题】

显然与 LCM 为一个循环节。那么只需取0~lcm来算就可以了。当 r 能被 b 整除时,直接特判解决。我们要关心的

所以直接求出0~lcm之间 b 的倍数个数(不计lcm在内(记为s),然后除以 r 的倍数的数量(记lcm在内(记为t)。就是将b的倍数分成 t 份,【即 s / t 就是所求。】

交上去,WA!为什么呢?因为这是将 s 化成 t 份。【如果 s 能被 t 整除,答案就是对的,如果 s 不能被 t 整除,那么必然有一个区间的数量是 s/t + 1 。】【特判一下就好】  

比如:5 14  

0 5 10 14 15 20 25 28 30 35 40 42  45  50  55 56  60 65 70  【可恶阿,没有想到这个又送了一题。】

 

 四十: Yet Another Monster Killing Problem 【贪心+思维】

看完题解之后:这个想法很绝。首先,我们需要预处理一个数组mx,其含义是:mx[ i ]代表能在一天内连续杀 i 个以上怪物的英雄的最大攻击力!

首先把英雄按耐力越大越前来排序(题意中的耐力值)。再从 n 枚举到 1 ,途中维护一个变量 t ,t = max( t , p[ i ] ) 。

【也就是说,我们枚举到耐力值 i 时,耐力值大于 i 的所有英雄中,最大的攻击力 = t 】使用 t 来更新mx[ i ]。

vector<PII>v;// fi 是耐力,se是攻击力
    sort(v.begin(),v.end(),greater<PII>());
    int j = 0,t = 0;
    for(int i = n;i;i--)  // 双指针维护mx数组就可以了,复杂度也不大。
    {
        while(j<m&&v[j].fi>=i)
            t = max(t,v[j++].se);
        mx[i] = t;
    }

然后O(n)解决问题:

int j = 1;t = 0;
    for(int i = 1;i <= n;i++)//i表示下一次要打的是第i个怪物
    {
        t = max(a[i],t); //维护被击杀怪物的最大攻击力,如果被杀过的怪物的攻击力比mx[j]大,说明不能连续杀j个了。
        if(t > mx[j])
            ans++,t=0,j=1,i--;//根据i的定义,t>mx[j]时,i还没有被击杀,所以i--
        else 
            j++;
    }

 

四十一:Berry Jam【前后缀思想+前缀和】

从中间往左右俩边走,一看就是左右分开思考。题目说剩下S和B酱要数量相等,易想到剩下的一定在左右两边。两种酱数量相等,就有一个巧妙地思维转换。把S的数字改成1,B的数字改成-1。这样把问题转换为去掉中间一个区间,左右区间的和等于0.

然后预处理左边数组的前缀和,记录sumL出现的最后的位置【这样才能使得吃的酱是最少的】

然后在计算右边的前缀和时【记得reverse,然后在sumR[ 0 ] = 0 开始】,其实就是后缀和,但我们reverse之后是前缀和。使用 "hash碰撞"  sumL的位置。 ans = min(ans , 2*n - i - sumL[ sumR ]);

        pre.clear();
    sub.clear();
    sum.clear();
    ans = 2*n;
    pre.emp(0);
    for(int i = 1;i <= n;i++)
    {
        read(t);
        if(t==1)pre.emp(1);
        else pre.emp(-1);
    }
    for(int i = 1;i <= n;i++)
    {
        pre[i]+=pre[i-1];
        sum[pre[i]] = i;
        read(t);
        if(t==1)sub.emp(1);
        else sub.emp(-1);
    }
    sub.emp(0);
    reverse(sub.begin(),sub.end());
    for(int i = 0;i <= n;i++)
    {
        if(i)sub[i] += sub[i-1];
        if(sum[-sub[i]]!=0||sub[i]==0)
            ans = min(ans,2*n-i-sum[-sub[i]]);
    }  
View Code

 

四十二:hacker cups and ball【线段树巧妙利用+二分答案】

 较好的题解

我的理解:对于一个序列 ,其排完序之后,数字是一定的。碍于暴力复杂度太大,所以不打算直接得到各个数字的位置,而是间接得到某一类数字的位置。接下来的思路就很巧妙了。

我们以大于等于中位数分为一类,记他们的值为1,小于的就记为0。然后排序的时候呢,就很好办了。如果是升序,那就是将所有的1放在右边,所有的0放在左边,降序相反即可。这样,我们便完成了 “分类意义上的排序”。然后最后排完序检查中位数的位置是不是1就好。【有这个思路已经很不错了。。可惜我一点不沾边】

 那么如何完成上面的想法呢?排序需要知道区间内1的个数,需要有修改区间的快速方法。??这不就是线段树吗?

好了,熟悉线段树的+思路活跃的基本就过了这道题了。

const int maxn = 2e5+11;
int n,a[maxn],m,L,R,mid,ans;
PII p[maxn];
int tr[maxn<<2],tag[maxn<<2];

inline void push_up(int ro){tr[ro] = tr[ls] + tr[rs];}

inline void build(int ro,int l,int r)
{
    if(l==r){
        tr[ro] = (a[l]>=mid);
        return ;
    }
    int mi = (l+r)>>1;
    build(ls,l,mi);
    build(rs,mi+1,r);
    push_up(ro);
}

inline void push_down(int ro,int l,int mi,int r)
{
    if(tag[ro]==-1){
        tag[ls] = tag[rs] = -1;
        tr[rs] = tr[ls] = 0;
    }else if(tag[ro]==1){
        tag[ls] = tag[rs] = 1;
        tr[rs] = r-mi;
        tr[ls] = mi-l+1;
    }
    tag[ro] = 0;
}

inline void update(int ro,int s,int e,int l,int r,int v)
{
    if(l <= s && e <= r)
    {
        tr[ro] = v*(e-s+1);
        tag[ro] = (v==0?-1:1);
        return ;
    }
    else if(r < l)return ;////////////////////
    int mi = (s+e)>>1;
    if(tag[ro])push_down(ro,s,mi,e);
    if(l <= mi)update(ls,s,mi,l,r,v);
    if(mi < r)update(rs,mi+1,e,l,r,v);
    push_up(ro);
}

inline int query(int ro,int s,int e,int l,int r)
{
    if(l <= s && e <= r)return tr[ro];
    int mi = (s+e)>>1,ans = 0;
    if(tag[ro])push_down(ro,s,mi,e);
    if(l <= mi)ans += query(ls,s,mi,l,r);
    if(mi < r)ans += query(rs,mi+1,e,l,r);
    return ans;
}

bool ok()
{
    me(tr,0),me(tag,0);
    build(1,1,n);
    for(int i = 1;i <= m;i++)
    {
        int mx = max(p[i].fi,p[i].se);
        int mn = min(p[i].fi,p[i].se);
        if(mn==mx)continue;/////////////////
        int sum = query(1,1,n,mn,mx);
        if(p[i].se > p[i].fi){
            update(1,1,n,mx-sum+1,mx,1);
            update(1,1,n,mn,mx-sum,0);
        }else {
            update(1,1,n,mn,mn+sum-1,1);
            update(1,1,n,mn+sum,mx,0);
        }
    }
    return query(1,1,n,n/2+1,n/2+1);
}

inline void solve()
{
    read(n,m);
    for(int i = 1;i <= n;i++)
        read(a[i]);
    for(int i = 1;i <= m;i++)
        read(p[i].fi,p[i].se);

    L = 1,R = n+1,ans=a[n/2+1];
    while(L<R)
    {
        mid = (L+R)>>1;
        if(ok())ans=mid,L=mid+1;
        else R=mid;
    }
    printf("%d\n",ans);
}
View Code

 

 四十三:模板--堆维护集合的所有子集中第k大的子集之和

Dreamoon and NightMarket【优先队列】题解

 一个较为直观的方法就是dfs枚举所有的物品 【取或是不取】,但是 2^n的复杂度你敢吗。

另外一个思路,使用BFS来枚举所有的物品【取或是不取】,然后使用priority_queue来优化。【BFS的优点就在于所有搜索几乎是同步进行的,所以 可以很方便地观察到下一步怎么走才是最合理】

枚举的方法:

【堆里变量的含义:pair<LL,LL> 当前和为first,该集合中所有元素中索引最大的是second 】

const int maxn = 2e5+11;
ll a[maxn],n,k;
inline void solve()
{
    read(n,k);
    for(int i = 1;i <= n;i++)read(a[i]);
    sort(a+1,a+1+n);
    ll ans=0;
    priority_queue<PLL,vector<PLL>,greater<PLL> >q;
    q.push(mp(a[1],1));
    while(!q.empty()&&k>0)
    {
        PLL it = q.top();q.pop();
        if(it.fi>=ans){
            ans = it.fi;
            k--;
        }
        if(it.se <= n)
            q.push(mp(it.fi+a[it.se+1],it.se+1)),
            q.push(mp(it.fi+a[it.se+1]-a[it.se],it.se+1));
    }
    printf("%lld\n",ans);
}
View Code

从第一个元素开始,依次加减元素形成二叉树!!!这种枚举子集的种类的方法不错!!!】

 

 四十四: Santa's Bot【基础概率题】

总概率由三个变量决定,即x、y、z。根据题目的思路来就好了,首先选一个小朋友,其概率为1/n,其次,这个小朋友有k个想要的礼物,我们选一个的可能是1/k,再次,合法的事件中z的小朋友必然也想要这个礼物y,所以我们累计一下每个礼物有多少小朋友想要,所以合法的概率就是:cnt[y]/n。答案就是  ∑ cnt[y]/(k*n*n)

 

四十五:Messenger Simulator【“动态?”前缀和!】

 “动态”什么鬼东西,我乱加的,以警示世人,不要再犯我这种错误了 。

考试的时候,我就想到,维护一个数组,一开始,数组中的序列是倒序的,然后就像反向队列的一样,如果有人发消息,那么就直接把他放在数列的最后面,然后原来的位置就设置为 0,新的位置设置为1,这样求一下和就知道最远排位了。

可是事与愿违,在上面维护序列的过程,数组的大小是不断变化的,然后我本来想用  线段树/树状数组   维护后缀和的,插入元素这一操作直接打消了我的想法。

 事实上,数组的大小最大是有限度的,就是n+m,所以一开始就建一个n+m的  线段树/树状数组   就好了。。。。。

 

四十六:Two Array【二维前缀和优化的DP / 组合数学】

定义状态:dp[2][ i ][ j ]  为第一个数列以 i 结尾,第二个数列以 j 结尾。然后 2 是滚动数组的意思。

转移方程: dp[now][ i ][ j ]  =  dp[pre][ i‘  <=  i ][ j' >=  j ] 也就是说,从上一个状态转移过来需要暴力循环求和

【其复杂度是O(n^2)】,这您是认真的吗?枚举结尾数字就要n^2了,您暴力求和还要n^2。这不得TLE阿。

事实上,我们可以一边维护前缀和,一边转移状态:【这是一时的灵感,不一定是正解,正解请看其它Blog】

for(int i = 1;i < m;i++)//m位数
{
    for(int j = 1;j <= n;j++)
    {
       for(int k = n;k >= 1;k--)//必须降到1才退出
        {
            dp[pre][j][k] = 
                (dp[pre][j][k]+dp[pre[j-1][k]+dp[pre][j][k+1]-dp[pre][j-1][k+1])%mod;
             if(k>=j)
                dp[now][j][k] = dp[pre][j][k];
        }
    }
}

 

四十七:Minimax Problem【状压+二分】

事实上,真的很难将这两者结合在一起阿。这里的二分跟上面的第四十二题有异曲同工之妙。首先需要注意到直接求解的难度,考虑一下验证答案呗!

那我们验证什么?验证那个最小的值呗!我们使用二分,验证时进行如下的操作:

①我们将一个序列中大于这个mid的位置设置为1,反之为0,然后用二进制存储。

②我们发现题目的求最大值其实就是互补,有点像  【与运算】 嘛。

好啦。做完了。

??怎么验证单调性??其实太刻意去验证单调性反而做不出来。如果mid不是最大的,那么它应该是存在两个序列 【与运算】之后,各个位都是1。那么我们就可以让他大一点。如果本来就不存在了,我们再增大mid,就更不可能了。

 

四十八:星球大战【逆向思维+并查集】

离线下来反向维护即可,①注意Find父亲时,变量不要混用。②一开始我求ans[m+1]的连通块的时候我是用DFS来求的,但是就是WA,不知道错哪了。。。。。。。。。

const int maxn = 5e5+11;
const ll mod = 1e9+7;
int n,m,a,b,ec;
bool br[maxn];
int fa[maxn],cnt,ans[maxn],bt[maxn];
vector<int>e[maxn];

inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

inline void solve()
{
    read(n,ec);
    for(int i = 1;i <= n;i++)fa[i]=i;
    for(int i = 1;i <= ec;i++){
        read(a,b);
        e[a].emp(b);
        e[b].emp(a);
    }
    read(m);
    for(int i = 1;i <= m;i++){
        ans[i] = 1;
        read(a);
        bt[i] = a;
        br[a] = 1;
    }
    cnt = n-m;
    for(int i = 1;i <= n;i++)
    {
        if(!br[i])
        for(int v:e[i])
            if(!br[v]&&Find(i)!=Find(v))
                fa[Find(v)] = Find(i),cnt--;;
    }

    ans[m+1] = cnt;
    for(int i = m;i;i--){
        br[bt[i]] = 0;
        cnt++;
        for(int v:e[bt[i]]){
            if(!br[v]){
                a = Find(bt[i]),b = Find(v);
                if(a!=b){
                    cnt--;
                    fa[a] = b;
                }
            }
        }
        ans[i] = cnt;
    }

    for(int i = 1;i <= 1+m;i++)
        printf("%d\n",ans[i]);
}
View Code

 

 四十九:Bitwise【二进制+分环+思维】

 ①出现了环   --->   将数组拓展为两倍

②分块后求&运算最大值 ----->每一块都在这一位上有1才行!!!

 ③分 k 块   --->  将二倍数组分成2*k块

             【但是有可能出现第一个数组末尾和第二个数组开头连在一起的情况,所以只需要分成2*k-1块】

     【如果不足2*k-1块,那么一定在环中分不够k块】

④根据第二点,我们贪心去判断每个数 在  第   i  位  上 存不存在 2*k-1 个 【 ones 】

合理性:由于左右两个数组是对称的,左边的分块在右边也会出现,如果有重复被使用,那么块数一定不足2*k-1

eg:  a1   a a3  a4  a1  a2  a3   a 【分2块】   

 假设现在判断到第 i 位,只有a1有,a2、a3、a4这一位是0。【假设现在答案是ans = a1 | a2 | a3,a4 = a2|a3】

 【 a1   a a3 】【 a4  a1 】 a2  a3   a4 后面就分不下去了。

 

 五十:WI konw【线段树】

这一题的线段树并不是建好之后直接query的,而是在求解的过程中不断更新树,从而维护到答案的正确性!!

const int maxn = 4e5+11,inf = 0x3f3f3f3f3f;
int a[maxn],las[maxn],cnt[maxn];
int tr[maxn<<2],n,m,top;

inline void Build(int ro,int l,int r)
{
    tr[ro]=inf;
    if(l==r)return;
    int mid = (l+r)>>1;
    Build(ls,l,mid);Build(rs,mid+1,r);
}

inline void update(int ro,int l,int r,int p)
{
    if(l==r){
        tr[ro] = a[p];
        return ;
    }
    int mid = (l+r)>>1;
    if(p <= mid)update(ls,l,mid,p);
    else update(rs,mid+1,r,p);
    tr[ro] = min(tr[rs],tr[ls]);
}

inline int query(int ro,int l,int r,int s,int e)
{
    if(s <= l && r <= e){
        return tr[ro];
    }else if(r<l)return inf;
    int mid = (l+r)>>1,ans = inf;
    if(s <= mid)ans = min(ans,query(ls,l,mid,s,e));
    if(mid < e)ans = min(ans,query(rs,mid+1,r,s,e));
    return ans;
}

inline void solve()
{
    read(n);
    Build(1,1,n);
    for(int i = 1;i <= n;i++)
        read(a[i]);
    for(int i = n;i;i--)
    {
        las[i] = cnt[a[i]];
        cnt[a[i]] = i;
    }
    PII ans = mp(inf,inf);
    for(int i = 1;i <= n;i++)
    {
        if(las[i]){
            int an = query(1,1,n,i+1,las[i]-1);//注意区间范围!
            ans = min(ans,mp(an,a[i]));
            update(1,1,n,las[i]);
        }
    }
    if(ans.fi!=inf)
        printf("%d %d\n",ans.fi,ans.se);
    else
        puts("-1");
}
View Code

空线段树update+query4】 

 

 五十一:(or)xor(and)【枚举每一位+组合数学】

 对每一个二进制位,使用一个计数器a、b,a记录数组中这一位上有一的数的数量,b记录数组中这一位是0的数的数量!

然后就是这样的几种情况:左边是1 xor 右边是 0 ,右边是1 xor 左边是0

然后组合数学列一下就可以了!

uint n,a[maxn];

inline void solve()
{
    read(n);
    uint ans = 0,tot = n*n;
    for(int i = 1;i <= n;i++)
        read(a[i]);
    for(uint bit = (1LL<<30);bit;bit>>=1)
    {
        int cnt = 0;
        for(int i = 1;i <= n;i++)
        if((a[i]&bit)==bit)cnt++;
        uint t1 = cnt*cnt;
        uint t2 = tot - t1;
        uint t3 = (n-cnt)*(n-cnt);
        uint t4 = tot - t3;
        ans += (t4*t2+t1*t3)*bit;
    }
    cout<<ans<<endl;
}
View Code

 

 五十二:平方运算【模除意义下平方出现循环节】

 其实平方+模运算是一定会出现循环节的!【只不过平时模数较大,这种现象不明显】

当模数较小的时候,我们就可以把循环节计算出来!【详见getloop();】

【题解!】

const int maxn = 1e5+11;
ll sum[maxn<<2][65],tr[maxn<<2];
bool in[maxn<<2],inc[maxn];
int vis[maxn],len[maxn<<2],pos[maxn<<2],nx[maxn],lop[maxn];
int n,m,p,a[maxn],tag[maxn<<2];

#define lcm(a,b) (1LL*a/__gcd(a,b)*b)
void getloop()
{
    for(int i = 0;i < p;i++)inc[i]=1,nx[i]=i*i%p;
    for(int i = 0;i < p;i++)
    {
        if(!vis[i]){
            int x = i,cnt = 0;//cnt记录长度!
            while(!vis[x]){
                cnt++;
                vis[x] = 1;
                x = nx[x];
            }
            for(int y = i;y!=x;y=nx[y])
                inc[y] = false,cnt--;
            //当x在环上且没有被赋值长度时,就进入循环!
            if(!lop[x]&&inc[x]){
                int y = nx[x];
                lop[x] = cnt;
                while(y!=x){
                    lop[y] = cnt;
                    y = nx[y];
                }
            }
        }
    }
}
inline void init(int ro,int v)
{
    tr[ro] = v;in[ro] = inc[v];
    if(in[ro]){
        len[ro] = lop[v];
        pos[ro] = 0;
        for(int i = 0;i < len[ro];i++,v=nx[v])
            sum[ro][i] = v;
    }
}

inline void push_up(int ro)
{
    //向上更新时!
    //in、tr、sum、len、pos五个数组都要更新!
    tr[ro] = tr[rs] + tr[ls];
    if(in[ls]&&in[rs]){
        in[ro] = true;
        len[ro] = lcm(len[ls],len[rs]);
        pos[ro] = 0;
        int P1 = pos[ls],P2 = pos[rs];
        for(int i = 0;i < len[ro];i++)
        {
            sum[ro][i] = sum[ls][P1++] + sum[rs][P2++];
            if(P1==len[ls])P1 = 0;if(P2==len[rs])P2 = 0;
        }
    }
}

inline void push_down(int ro)
{
    //注意取模
    tag[rs] += tag[ro];
    pos[rs] = (pos[rs]+tag[ro])%len[rs];
    tr[rs] = sum[rs][pos[rs]];

    tag[ls] += tag[ro];
    pos[ls] = (pos[ls]+tag[ro])%len[ls];
    tr[ls] = sum[ls][pos[ls]];

    tag[ro] = 0;
}

inline void modify(int ro)
{
    //注意归零!取模就行
    if(pos[ro]+1==len[ro])tr[ro] = sum[ro][0],pos[ro]=0;
    else tr[ro] = sum[ro][++pos[ro]];
    tag[ro]++;
}

inline void Build(int ro,int l,int r)
{
    if(l==r){init(ro,a[l]);return ;}
    Build(ls,l,mid_seg);
    Build(rs,mid_seg+1,r);
    push_up(ro);
}

inline void update(int ro,int l,int r,int s,int e)
{
    if(s <= l && r <= e && in[ro]){modify(ro);return;}
    if(l==r){init(ro,nx[tr[ro]]);return ;}
    if(tag[ro])push_down(ro);///////////////////////////////
    if(s <= mid_seg)update(ls,l,mid_seg,s,e);
    if(mid_seg < e)update(rs,mid_seg+1,r,s,e);
    push_up(ro);
}

inline ll query(int ro,int l,int r,int s,int e)
{
    if(s <= l && r <= e){
        return tr[ro];
    }
    if(tag[ro])push_down(ro);
    ll ans = 0;
    if(s <= mid_seg)ans += query(ls,l,mid_seg,s,e);
    if(mid_seg < e)ans += query(rs,mid_seg+1,r,s,e);
    return ans;
}

inline void solve()
{
    read(n,m,p);
    getloop();
    for(int i = 1;i <= n;i++)
        read(a[i]);
    Build(1,1,n);
    int op,l,r;
    for(int i = 1;i <= m;i++)
    {
        read(op,l,r);
        if(op==1){
            update(1,1,n,l,r);
        }else{
            printf("%lld\n",query(1,1,n,l,r));
        }
    }
}
View Code

 

五十三:Fliptile【翻转问题】

 暴力+二进制表示状态:

①直接想很难做!观察到n很小!那要不像状压DP一样,用二进制代表那个点是要改的吧!【理论支撑/直觉:只要我们枚举出第一行我们踩那些点,然后如果第一行踩完之后,还有是1的点,那么这些点只能在第二行解决!所以我们只要得到上一行的状态,就可以知道下一行要踩那些点了!(然后最后验证一下啊!)】

const int maxn = 20,inf = 0x3f3f3f3f;
int n,m,k,a[maxn],b[maxn],c[maxn],ans[maxn],mn;

inline bool check()
{
    for(int i = 0;i < m;i++)
        if(c[i])return false;
    return true;
}

const int dx[]={0,1,-1,0,0},dy[]={0,0,0,-1,1};

inline void modify(int x,int y)
{
    int x2,y2;
    for(int i = 0;i < 5;i++)
    {
        x2 = x + dx[i];
        y2 = y + dy[i];
        if(x2>=0&&y2>=0&&x2<m&&y2<n){
            c[x2] ^= (1<<y2);
        }
    }
}

inline int flip(int x)
{
    b[0] = x;
    int res = 0;
    for(int i = 0;i < m;i++)
    {
        for(int j = 0;j < n;j++)
            if((b[i]>>j)&1)
                modify(i,j),res++;
        for(int j = 0;j < n;j++)
        {
            if((c[i]>>j)&1)
            b[i+1] |= (1<<j);
        }
    }
    if(check())return res;
    else return inf;
}

inline void solve()
{
    mn = inf;
    for(int i = 0;i < m;i++)
    for(int j = 0;j < n;j++)
    {
        read(k);
        a[i] |= (k<<j);
    }
    int lim = (1<<n);
    for(int i = 0;i < lim;i++)
    {
        for(int j = 0;j < m;j++)
            c[j] = a[j],b[j] = 0;
        int cnt = flip(i);
        if(cnt<mn)
        {
            mn = cnt;
            for(int t = 0;t < m;t++)
                ans[t] = b[t];
        }
    }
    if(mn==inf){
        puts("IMPOSSIBLE");
        return ;
    }
    for(int i = 0;i < m;i++)
    {
        for(int j = 0;j < n;j++)
        {
            if((ans[i]>>j)&1)printf("1 ");
            else printf("0 ");
        }
        pln;
    }
}
View Code

 

 五十四:wcy的狗狗【二分的运用+双指针+排列组合+思维】

 ①根据题目给出的三个关键点,按照升序排序,可以看出单调性,【而且这个亲密度跟 j-i 有关,二分更加容易做了。】

 ②想到二分出亲密度之后,还要会排列组合,逆向思维求出亲密度小于等于mid的数量:

=O(n)做法

总点对数: ( n − x ) ∗ x + x ∗ ( x − 1 ) / 2 (n - x) * x + x * (x - 1) / 2(n−x)∗x+x∗(x−1)/2

 ③最后再for循环一遍,从后往前找就是了【注意二分的范围以及同色点对的累计!】

【这个排列组合真的难死我了。。。。】

const int maxn = 1e5+11,inf = 0x3f3f3f3f;
ll n,k,a[maxn],cnt[maxn];
PII ans = mp(inf,inf);

ll get(ll mid)
{
    ll tot = (n-mid)*(mid) + (mid-1)*mid/2,ans = 0;
    me(cnt,0);
    int P = 1;
    for(int i = 1;i <= n-mid;i++)
    {
        while(P<=i+mid&&P<=n)
        {
            //做过莫队的题目应该就能想到了
            ans += cnt[a[P++]]++;
        }
        --cnt[a[i]];
    }
    return tot - ans;
}

inline void solve()
{
    read(n,k);
    for(int i = 1;i <= n;i++)
    {
        read(a[i]);
    }
    int L = 1,R = n,mid,want=inf;
    while(L<R)
    {
        mid = (L+R)>>1;
        ll t = get(mid);
        if(t<=(1LL*n*(n-1)/2)&&t>=k)R = mid,want = mid;
        else L = mid+1;
    }
    if(want==inf){
        puts("-1");
        return ;
    }
    ll num = get(want) - k;//找出从后往前第几个
    for(int i = n-want;i;i--)//逆序找
    {
        if(a[i]!=a[i+want]){
            if(num==0){
                ans.fi = i,ans.se = i+want;
                break;
            }
            num--;
        }
    }
    printf("%d %d\n",ans.fi,ans.se);
}
View Code

 

五十五:树上最大中位数【二分+01状态表示】

 也不是第一次使用二分+01解决中位数问题了吧,但是这一题还是想地不够深入。

一开始我以为任何时候都能左右横跳,然后使得最大中位数要么是最大值,要么是第二大值。这显然是不对的。

①我们二分一个mid,然后大于等于mid的顶点设置为1,小于mid设置为0。

②如果 连续两个节点出现1、1,那么我们通过左右横跳一定可以使得中位数更大。

③所以我们需要创造出一条没有1、1相邻的路径

④根据中位数的定义,在路径上必须有 k/2+1 个节点的值是 1 ,其余是 0。【我懵了很久,不知道怎么做,然后发现有一种DFS的方法可以验证从 s 到 t 是否存在一条路径上0、1的个数是符合中位数定义的

⑤这一题二分区间一定要设置为【0~mx+1】,不能把mx换成1e9【否则要么TLE,要么WA】(不知道原因。。)。

void DFS(int x,int mid)
{
    f[x] = 1;
    for(int v:e[x]){
        if(!f[v] && max(a[v],a[x])>=mid){
            DFS(v,mid);
        }
    }
}
//然后最后return F[t]就可以了,只有0、1相间的情况才能从s到t【1、1相邻的情况被BFS排除了】
#include <stdio.h>
#include <iostream>
#include <ctime>
#include <iomanip>
#include <cstring>
#include <algorithm>
#include <queue>
//#include <chrono>
//#include <random>
//#include <unordered_map>
//#pragma GCC optimize(3,"inline","Ofast")
#include <cmath> // cmath里面有y1变量,所以外面不能再声明
#include <assert.h>
#include <map>
#include <set>
#include <stack>
#define lowbit(x) (x&(-x))//~为按位取反再加1
#define re register
#define mid_seg ((l+r)>>1)
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define lld long double
#define uint unsigned int
#define ull unsigned long long
#define fi first
#define se second
#define pln puts("")
#define deline cerr<<"-----------------------------------------"<<endl
#define de(a)  cerr<<#a<<" = "<<a<<endl
#define de2(a,b) de(a),de(b),cerr<<"----------"<<endl
#define de3(a,b,c) de(a),de(b),de(c),cerr<<"-----------"<<endl
#define de4(a,b,c,d) de(a),de(b),de(c),de(d),cerr<<"-----------"<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define me(a,b) memset(a,(b),sizeof a)
#define PII pair<int,int>
#define PLL pair<ll,ll>
#define mp make_pair
using namespace std;
inline ll max(ll a,ll b){return (a>b)?a:b;}
inline ll min(ll a,ll b){return (a>b)?b:a;}
template<typename T>
inline void read(T&res){ //
    ll x=0,f=1;char ch;
    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();
    }res = x*f;
}
template<typename T,typename...Args>inline
void read(T&t,Args&...a){read(t);read(a...);}

const int maxn = 2e5+11,inf = 0x3f3f3f3f;
int a[maxn],n,m,s,t,vis[maxn];
vector<int>e[maxn];

bool DFS(int x,int mid)
{
    if(vis[x])return false;
    vis[x] = 1;
    if(x==t)return true;
    for(int v:e[x]){
        if(!vis[v] && max(a[v],a[x])>=mid){
            if(DFS(v,mid))return true;
        }
    }
    return false;
}

bool ok(int mid)
{
    me(vis,-1);
    queue<int>q;
    q.push(s);
    if(a[s]>=mid)vis[s] = 1;
    else vis[s] = 0;
    while(!q.empty())
    {
        int x = q.front();q.pop();
        for(int v:e[x])
        {
            if(vis[v] == -1){
                if(a[v]>=mid)vis[v]=1;
                else vis[v] = 0;
                q.push(v);
            }
            if(vis[v]==1&&vis[x]==1)
                return true;
        }
    }
    me(vis,0);
    return DFS(s,mid);
}

inline void dfs(int x){vis[x] = 1;for(int v:e[x])if(!vis[v])dfs(v);}

inline void solve()
{
    read(n,m,s,t);
    int mx = 0;
    for(int i = 1;i <= n;i++)
        read(a[i]),e[i].clear(),vis[i]=0,mx=max(mx,a[i]);
    for(int i = 1;i <= m;i++){
        int x,y;
        read(x,y);
        e[x].emp(y);
        e[y].emp(x);
    }
    dfs(s);
    if(vis[t]){
        puts("YES");
        int L = 0,R = mx+1,mid,ans=0;
        while(L<R)
        {
            mid = (L+R)>>1;
            if(ok(mid))ans=mid,L=mid+1;
            else R=mid;
        }
        printf("%d\n",ans);
    }else puts("NO");
}

int main()
{
    int t = 1;
    //cin>>t;
    ~scanf("%d",&t);
    //getchar();
    while(t--)
        solve();
}
View Code

 

 五十六:植物大战僵尸【二分+思维】

 最大化最小值,首先想到二分最小值,那么如何验证呢?

贪心验证:直接从左到右浇水,左右横跳的方式可以给后面留出更大的步数/移动空间。由于机器人是从x=0处开始出发的,所以直接视为从1走到n,每一次走到第i位,如果bi是大于1的【即需要浇水】,那么就在浇第i位的同时,顺便浇第i+1位,这样来回横跳,浪费的水是最少的。

bool ok(ll mid)
{
    ull sum = 0;
    for(int i = 1;i <= n;i++)
        b[i]=(mid%a[i]?mid/a[i]+1:mid/a[i]);
    for(int i =1;i <= n;i++)
    {
        if(i==n&&b[i]<1)break;
        sum++;//从上一个格子,走到这个格子
        if(b[i]>1){
            sum += (b[i]-1)*2;//左右 横跳
            b[i+1] -= (b[i]-1);//下一个格子也被浇水了
        }
        if(sum>m)return false;
    }
    return sum <= m;
}
View Code

 

 五十七:二维离散化【模板】

调了半天。。。

注意点:

①一定要将x、y分开离散化,得到R,C,一开始hx,hy数组不需要提前加入数字!!!【因为在加入离散化数字时,会有一个-1~1的波动,不需要加入边界!,但是如果题目说有边界,那么只需要在加入离散化的时候判断一下就好】

②然后建矩阵时,vector<vector<int>>G(R+1,vector<int>(C+1,0))【如果想要从0开始就不需要加1】

然后判断越界时:x>=0&&y>=0&&x<R&&y<C【R,C  没有等号】

class Solution {
public:
    #define emp emplace_back
    #define PII pair<int,int>
    #define fi first
    #define se second
    #define mp make_pair
    const int dx[4]={0,0,1,-1};
    const int dy[4]={1,-1,0,0};
    const int N = 1e6;

    bool bound(int x){
        return x>=0&&x<N;
    }
    bool bound(int x,int y,int R,int C){
        return x>=0&&y>=0&&x<R&&y<C;
    }

    bool isEscapePossible(vector<vector<int>>&B,vector<int>&S,vector<int>&TT) {
        vector<int>hx,hy;
        for(auto&p:B){
            for(int k=-1;k<=1;k++){
                int x = p[0]+k;
                int y = p[1]+k;
                if(bound(x))hx.emp(x);
                if(bound(y))hy.emp(y);
            }
        }
        for(int k=-1;k<=1;k++){
            int x = TT[0]+k;
            int y = TT[1]+k;
            if(bound(x))hx.emp(x);
            if(bound(y))hy.emp(y);
            x = S[0]+k;
            y = S[1]+k;
            if(bound(x))hx.emp(x);
            if(bound(y))hy.emp(y);
        }
        sort(hx.begin(),hx.end());
        sort(hy.begin(),hy.end());
        hx.erase(unique(hx.begin(),hx.end()),hx.end());
        hy.erase(unique(hy.begin(),hy.end()),hy.end());
        int R = hx.size(),C = hy.size();

        vector<vector<int> >G(R,vector<int>(C,0));

        for(auto&p:B){
            int x = lower_bound(hx.begin(),hx.end(),p[0])-hx.begin();
            int y = lower_bound(hy.begin(),hy.end(),p[1])-hy.begin();
            G[x][y] = -1;
        }
        int sx = lower_bound(hx.begin(),hx.end(),S[0])-hx.begin();
        int sy = lower_bound(hy.begin(),hy.end(),S[1])-hy.begin();
        int ex = lower_bound(hx.begin(),hx.end(),TT[0])-hx.begin();
        int ey = lower_bound(hy.begin(),hy.end(),TT[1])-hy.begin();
        
        G[sx][sy] = -1;
        queue<PII>q;
        q.push(mp(sx,sy));
        while(!q.empty()){
            int x=q.front().fi,y=q.front().se,tx,ty;
            q.pop();
            for(int i=0;i<4;i++){
                tx = x + dx[i];
                ty = y + dy[i];
                if(bound(tx,ty,R,C)&&G[tx][ty]!=-1){
                    q.push(mp(tx,ty));
                    G[tx][ty] = -1;
                }
                if(tx==ex&&ty==ey)return true;
            }
        }
        return false;
    }
};
View Code

 【LUOGU 讨论贴题目

 

 五十八:石子游戏【差分】

 这道题的类似题目之前有做过,当一个数组数字完全相等,差分数组为0。明显,我们只需要贪心地消掉差分数组就可以了。

坑点:

①m==1或n==1时特判一下!

②i%m==0时也要分开讨论,从后面往前面消,如果遇到小于0的,那么必然消不掉,return  -1就可以了。【为了避免n%m==0,所以要从(n-1)/m*m开始而不是从n/m*m】

const int maxn = 5e5+11;
const ll inf = 0x3f3f3f3f3f3f3f;
ll n,m,d[maxn],a[maxn],mx;

ll get()
{
    ll cnt = 0;
//特判1
    if(m==1){
        for(int i = 1;i <= n;i++)
            cnt += mx-a[i];
        return cnt;
    }
//特判2
    for(int i = (n-1)/m*m;i >= m;i-=m)
    {
        if(d[i]>=0){
            d[i-m] += d[i];
            cnt += abs(d[i]);
            d[i] = 0;
        }
        else return -1;
    }
//处理3
    for(int i = 1;i < n;i++)
    {
        if(i+m<=n&&i%m!=0){
            if(d[i]<=0){
                cnt += abs(d[i]);
                d[i+m] += d[i];
                d[i] = 0;
            }else return -1;
        }
        else if(d[i])return -1;
    }
//返回结果
    return cnt;
}

inline void solve()
{
    read(n,m);
    mx = 0;
    for(int i = 1;i <= n;i++)
    {
        read(a[i]);
        d[i-1] = a[i] - a[i-1];
        mx = max(a[i],mx);
    }
    d[n] = inf,d[0] = -inf;


    ll ans = get();
    printf("%lld\n",ans);
}
View Code

 

 五十九:国王的游戏【高精+思维】

考虑两个位置之间的关系,一定要像这一题一样考虑!【luogu第一篇题解很好!】

class BigNumber{
public:
    const int maxn = 5e4+11;//不能超过1e5
    int a[maxn],len;
    BigNumber():len(0){me(a,0);};
    BigNumber(ll x){
        len = 0;
        while(x){
            a[len++] = x%10;
            x/=10;
        }
    }
    BigNumber(const BigNumber&b){
        len = b.len;
        for(int i = 0;i < len;i++)
            a[i] = b.a[i];
    }
    BigNumber operator=(const BigNumber&b){
        len = b.len;
        for(int i = 0;i < len;i++)
            a[i] = b.a[i];
        return *this;
    }
    BigNumber operator*(int num){
        ll carry = 0;
        BigNumber ans;
        ans.len = len;
        for(int i = 0;i < len;i++){
            ans.a[i] += a[i]*num + carry;
            carry = ans.a[i]/10;
            ans.a[i] %= 10;
        }
        while(carry){
            ans.a[ans.len++] = carry%10;
            carry /= 10;
        }
        while(ans.a[ans.len]>0)ans.len++;
        return ans;
    }
    bool operator>(const BigNumber&b)const{
        if(len==b.len){
            for(int i = len-1;i>=0;i--){
                if(a[i]>b.a[i])return true;
                if(a[i]<b.a[i])return false;
            }
            return false;
        }
        return len > b.len;
    }
    BigNumber operator/(int num){
        BigNumber ans;
        ll temp = 0;
        for(int i = len-1;i>=0;i--){
            temp *= 10;
            temp += a[i];
            ans.a[i] = temp / num;
            if(ans.len==0&&ans.a[i]!=0){
                ans.len = i;
            }
            temp %= num;
        }
        if(ans.len==0)ans.len=1;
        while(ans.a[ans.len]>0)ans.len++;
        return ans;
    }
    void put(){
        while(a[len-1]==0&&len>1)len--;
        for(int i = len-1;i>=0;i--)
            printf("%d",a[i]);
        pln;
    }
};
高精【大数类与long long之间的乘法+除法】

 

【线性基模板】

目前仍然不知道正交补有什么用,反正没考过

struct LBase {
  static constexpr int bit = 32;
  ll p[70], fail;
  LBase() : fail(false) { fill(p, p + bit, 0); }
  bool insert(ll val) {
    for (int i = bit - 1; ~i; i--)
      if ((1ll << i) & val) {
        if (!p[i]) return p[i] = val, true;
        val ^= p[i];
      }
    return !(fail = true);
  }
  bool find(ll val) {
    if (val <= 0) return val < 0 ? false : fail;
    for (int i = bit - 1; ~i; i--)
      if ((1ll << i) & val) {
        if (!p[i]) return false;
        val ^= p[i];
      }
    return true;
  }
  LBase Union(const LBase &b) {  // 并集,U要大写*
    for (int i = bit - 1; ~i; i--) insert(b.p[i]);
    return *this;
  }
  friend LBase complement(LBase a) {  // 求正交补[反集]
    LBase ret;
    for (int i = bit - 1; ~i; i--)
      for (int j = i - 1; ~j; j--) a.p[i] = min(a.p[i], a.p[j] ^ a.p[i]);
    for (int i = 0; i < bit; i++) {
      if (a.p[i]) continue;
      ll tmp = (1ll << i);
      for (int j = i + 1; j < bit; j++)
        if (a.p[j] & (1ll << i)) tmp += 1ll << j;
      ret.insert(tmp);
    }
    return ret;
  }
  friend LBase Intersection(const LBase &a, const LBase &b) {  // 交集
    LBase ret, d = a, all = a;  // all装的是目前所有可用的低位基
    // k是把baseBi和它的低位帮手削减至0所用到的baseA的异或和
    for (int i = 0; i < bit; i++) {
      if (b.p[i] == 0) continue;
      ll v = b.p[i], k = 0, j;
      for (j = i; j >= 0; j--) {
        if (((1ll << j) & v)) {
          if (all.p[j] == 0) break;
          v ^= all.p[j], k ^= d.p[j];
        }
      }
      if (!v)
        ret.p[i] = k;
      else {
        all.p[j] = v;
        d.p[j] = k;
      }
    }
    return ret;
  }
};
View Code

 

 

六十:牛牛数数【二分+线性基】

线性基借助求第k小的数,tot-mid得到大于第k小的数有多少个!

理解:

【1】tot  = (1<<cnt) - 1 + fail 【如果不存在0时,总数就是1<<cnt-1,如果有0,就需要 加1】

【2】work函数干嘛呢?其实是一个rebuild的过程,我们知道,一个序列的线性基并不是唯一的,我们通过rebuild

把 d[ i ] 上含有 1 的位置都与小于 i 的元素异或一遍,最后得到的 d[ i ] 上的 1 都是独一无二的!!

比如说:

5(4+1) 、 11(8+2+1)、21(16+4+1)

rebuild得:5、11、17

rebuild之后他们能组合成:

①5  (1,0,0)        

②11(0,1,0)

③14(1,1,0)

④17 (0,0,1)

⑤20 (1,0,1)

⑥26 (0,1,1)

⑦30 (1,1,1)

我们发现rebuild完之后,一个数的排名就和二进制相关了!!【不rebuild是不存在这样的联系的,可以手玩试试!】

const int maxn = 1e5+11,inf  = 0x3f3f3f3f;
ll n,m,K,a[maxn],b[100],c[100],cnt,tot;
bool fail;

inline void insert(ll x)
{
    for(int i = 61;i>=0;i--)
        if(x&(1LL<<i)){
            if(b[i])
                x ^= b[i];
            else{
                b[i] = x;
                return ;
            }
        }
    fail = true;
}

inline void work()
{
    //预处理
    for(int i = 0;i <= 62;i++)
    for(int j = 0;j < i;j++)
    if(b[i]&(1LL<<j))b[i]^=b[j];
    //
    for(int i = 0;i <= 61;i++)
        if(b[i])c[cnt++] = b[i];
    //总数
    tot = (1LL<<cnt) + fail - 1;
}

ll k_min(ll k)
{
    if(fail)k--;
    if(!k)return 0;
    if(k>=tot)return -1;
    ll ans = 0;
    for(int i = 0;i < cnt;i++)
    if(k&(1LL<<i))ans ^= c[i];
    return ans;
}

inline void solve()
{
    read(n,K);
    me(a,0),me(b,0),me(c,0);
    for(int i = 1;i <= n;i++){
        read(a[i]);
        insert(a[i]);
    }
    work();
    ll L = 0,R = tot,mid,ans;
    while(L<R)
    {
        mid = (L+R)>>1;
        if(k_min(mid) > K)ans=mid,R=mid;
        else L=mid+1;
    }
    ans = tot - ans + 1;
    printf("%lld\n",ans);
}
View Code

 

 六十一:幸运数字【树上LCA+线性基合并】

 一看题目懵逼了很久 。

①树上倍增LCA忘了。。。【去翻了翻板子】

②woc?线性基怎么合并?在树上线性基又怎么合并?

解决方法:普通的合并其实很简单,就是将一个线性基里的元素insert进去另一个线性基里面就可以了。

【如果熟练倍增LCA的话,就更好办了】

①我们预处理LCA需要两步,(1)dfs求深度dep数组,记录up[x][0] 父节点 (2)pre函数进行DP转移!

②然后 在我们求LCA的时候会经过一条路径,我们记录这条路径就可以了!

坑点:max_element函数里面求最大值时,if((ans^d[i]) > ans) 别忘了括号!!

【我觉得自己的代码还是封装得挺好的(自恋)】

const int maxn = 2e4+11,inf  = 0x3f3f3f3f;
ll n,m,a,b,c,w[maxn],dep[maxn];
struct node
{
    ll fa,d[65];
    node(){fa = 0,me(d,0);}
    void insert(ll x)
    {
        for(int i = 62;i >= 0;i--)
            if(x&(1LL<<i))
            if(d[i])x ^= d[i];
            else{d[i] = x;return ;}
    }
    void merge(const node&s)
    {
        for(int i = 0;i <= 62;i++)
            if(s.d[i])this->insert(s.d[i]);
    }
    ll max_element()
    {
        ll ans = 0;
        for(int i = 62;i >= 0;i--)
            if((d[i]^ans)>ans)
                ans ^= d[i];
        return ans;
    }
    void ini(){
        fa = 0,me(d,0);
    }
}s[maxn][20],T;
vector<int>e[maxn];

void dfs(int x,int fa)
{
    s[x][0].insert(w[x]);
    s[x][0].insert(w[fa]);
    for(int v:e[x])
        if(v!=fa){
            dep[v] = dep[x] + 1;
            s[v][0].fa = x;
            dfs(v,x);
        }
}

void pre()
{
    for(int k = 1;k < 20;k++)
    {
        for(int i = 1;i <= n;i++)
        {
            s[i][k].fa = s[s[i][k-1].fa][k-1].fa;
            s[i][k].merge(s[s[i][k-1].fa][k-1]);
            s[i][k].merge(s[i][k-1]);
        }
    }
}

int LCA(int x,int y)
{
    T.insert(w[x]);
    T.insert(w[y]);
    if(dep[x] < dep[y])swap(x,y);
    int c = dep[x] - dep[y];
    for(int i = 19;i >= 0;i--)
        if(c&(1<<i))
        {
            T.merge(s[x][i]);
            x = s[x][i].fa;
        }
    if(x==y)return x;
    for(int i = 19;i >= 0;i--)
    {
        if(s[x][i].fa != s[y][i].fa)
        {
            T.merge(s[y][i]);
            T.merge(s[x][i]);
            y = s[y][i].fa;
            x = s[x][i].fa;
        }
    }
    return s[x][0].fa;
}

inline void solve()
{
    read(n,m);
    for(int i = 1;i <= n;i++)
        read(w[i]);
    for(int i = 1;i < n;i++)
    {
        read(a,b);
        e[a].emp(b);
        e[b].emp(a);
    }
    dfs(1,0);
    pre();
    while(m--)
    {
        T.ini();
        read(a,b);
        int lca = LCA(a,b);
        T.insert(w[lca]);
        printf("%lld\n",T.max_element());
    }
}
View Code

 

六十二:线性基能组成多少个数字 【模板:线性基】

 线性基的基本性质:

线性基元素异或所能得到的数字个数   ==   (1<<cnt) 【cnt就是成功插入线性基的元素个数!】

 

 六十三:生日悖论【组合数学】

首先  ∏ Cisum  算出从sum个人里,选出n组人的概率,然后再C(365,n) 从365天选取n天,最后把所有人数相同的组视为同一种元素,把日期分给组数,相当于:∏C(n,cnt[i]) 算出从n天里选一个集合给【同人数的组】,因为把人数相同的组视为同一种元素,所以这样才不会重复!

 

 六十四:正十字【时间戳优化暴力+二分+思维】

首先,把一个正十字在障碍中移动,可以转换为【把障碍都换成正十字,然后把一个点移动到另一个点】

然后,题目要求取最大值,那么我们二分一个答案来【使用并查集维护连通性】验证就可以大大简化问题了!

最后,看到询问次数很多!然后考虑到二分的矩形图基本上变化不大,可以考虑离线下来,增加每一个矩形图和并查集的利用率!

 

const int maxn = 1011,inf  = 0x3f3f3f3f;
const int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
int t[maxn][maxn],fa[maxn*maxn];
int n,m,a,b,c,k,now_mid;
int ans[100005];
bool vis[maxn][maxn];
struct ASK
{
    int sx,sy,ex,ey,mid,L,R,i;
    bool operator<(const ASK&a)const{
        return mid > a.mid;
    }
}s[100005];
vector<PII>v[maxn];

inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
inline int get(int i,int j){return j+(i-1)*m;}
inline void init_set()
{
    for(int i = 1;i <= n;i++)
    for(int j = 1;j <= m;j++)
        fa[get(i,j)] = get(i,j),vis[i][j]=0;
}

inline void unite(int x,int y)
{
    x = Find(x),y = Find(y);
    if(x!=y)fa[x] = y;
}

inline void add(int x,int y)
{
    vis[x][y] = 1;
    if(y!=1&&vis[x][y-1]) unite((get(x,y-1)),Find(get(x,y)));
    if(y!=m&&vis[x][y+1]) unite((get(x,y+1)),Find(get(x,y)));
    if(x!=1&&vis[x-1][y]) unite((get(x-1,y)),Find(get(x,y)));
    if(x!=n&&vis[x+1][y]) unite((get(x+1,y)),Find(get(x,y)));
}

inline void rebuild(int len)
{
    while(now_mid>len&&len>=0)
    {
        for(auto&p:v[now_mid])
        {
            add(p.fi,p.se);
        }
        now_mid--;
    }
}

inline void work()
{
    for(int i = 1;i <= k;i++)
    {
        if(s[i].L==s[i].R)continue;
        rebuild(s[i].mid);
        if(Find(get(s[i].sx,s[i].sy))==Find(get(s[i].ex,s[i].ey)))
        {
            ans[s[i].i] = now_mid;
            s[i].L = now_mid+1;
        }
        else s[i].R = now_mid;
        s[i].mid = (s[i].R+s[i].L)>>1;
    }
}

inline void solve()
{
    read(n,m,k);
    me(t,inf);
    int ln = max(n,m);
    for(int i = 1;i <= n;i++)
    for(int j = 1;j <= m;j++)
    {
        read(a);
        t[i][j] = min({t[i][j],n-i+1,i,m-j+1,j});
        if(a){
            t[i][j] = 0;
            for(int k=1;i+k<=n;k++)
                if(k<=t[i+k][j])t[i+k][j]=k;
                else break;
            for(int k=1;i-k>0;k++)
                if(k<=t[i-k][j])t[i-k][j]=k;
                else break;
            for(int k=1;j-k>0;k++)
                if(k<=t[i][j-k])t[i][j-k]=k;
                else break;
            for(int k=1;j+k<=m;k++)
                if(k<=t[i][j+k])t[i][j+k]=k;
                else break;
        }
        if(t[i][j]>0)
            v[t[i][j]].emp(mp(i,j));
    }

    for(int i = 1;i <= k;i++)
    {
        ans[i] = -1;
        read(s[i].sx,s[i].sy,s[i].ex,s[i].ey);
        s[i].i = i;
        s[i].L = 0,s[i].R = ((n+1)>>1)+1;
        s[i].mid = (s[i].L+s[i].R)>>1;
    }

    for(int i = 1;i <= 15;i++)
    {
        init_set();
        now_mid = ((n+1)>>1)+1;
        sort(s+1,s+1+k);
        work();
    }

    for(int i = 1;i <= k;i++)
        printf("%d\n",ans[i]);
}
View Code

 

这个问题的实现是很困难的,主要难在:

①不知道怎么样将这么多个点同时二分!

②时间戳在变化的时候,我们应该怎么维护这个矩形图

题解

一开始维护一个t数组【表示什么时候这个点可以加入并查集】我们把时间戳从高到低不断地减下来,不断地add点,【因为并查集加点比删点简单,所以考虑从高到低】,然后所有的询问都自己做自己的二分,互相之间并无联系,只是增加了同一个图的利用率而已!

 

 六十五:CCF:窗口【排序题】

 不要想着O(n^2)去模拟,而是直接判断  点击的点  从顶层到底层逐一判断在不在窗口里面即可!

 

 六十六:NOIP提高组~列队【01线段树 / 01树状数组排序】

 本题涉及到线段树的动态开点,和【四十五题】有点相似,但是此题更难,因为涉及到n个序列,同时还有第m列分开考虑。

【不过这些东西在动态开点之下都显得很简单了!!动态开点牛逼!!】

【还有  l ~ r 的含义真的很妙,根据cnt的不同,得到的l~r是不一样的!!!但是都是正确的排序!!】

const int maxn = 5e5+11,maxm = 1e7+11;
ll n,m,q,x,y;
struct seg_tree
{
    ll id,cnt;
    int ls,rs;
    seg_tree(ll _cnt=0){
        ls=rs=id=0;
        cnt = _cnt;
    }
}tr[maxm];
int root[maxn],top,used[maxn];

inline ll query(int ro,int l,int r,ll p,ll&res,bool tp)
{
    if(l==r){
        if(tp){
            res = (tr[ro].id?tr[ro].id:l*m);//L才是当前的排名
        }else{
            res = (tr[ro].id?tr[ro].id:(x-1)*m+l);//同理L
        }
        return l;
    }
    if(!tr[ro].ls){
        tr[ro].ls = ++top;
        tr[top] = seg_tree(mseg-l+1);
        tr[ro].rs = ++top;
        tr[top] = seg_tree(r-mseg);
    }
    if(tr[tr[ro].ls].cnt >= p)
        return query(tr[ro].ls,l,mseg,p,res,tp);
    else
        return query(tr[ro].rs,mseg+1,r,p-tr[tr[ro].ls].cnt,res,tp);
}

inline void modify(int ro,int l,int r,ll p,ll id,ll v)
{
    if(l==r){
        tr[ro].id = id;
        tr[ro].cnt = v;
        return ;
    }
    if(!tr[ro].ls){
        tr[ro].ls = ++top;
        tr[top] = seg_tree(mseg-l+1);
        tr[ro].rs = ++top;
        tr[top] = seg_tree(r-mseg);
    }
    if(p <= mseg)modify(tr[ro].ls,l,mseg,p,id,v);
    else modify(tr[ro].rs,mseg+1,r,p,id,v);
    tr[ro].cnt = tr[tr[ro].ls].cnt + tr[tr[ro].rs].cnt;
}

inline void solve()
{
    read(n,m,q);
    for(int i = 1;i <= n;i++){
        root[i] = ++top;
        tr[top] = seg_tree(m-1);
    }
    root[n+1] = ++top;
    tr[top] = seg_tree(n);
    for(int i = 1;i <= q;i++)
    {
        read(x,y);
        ll res,cur,p1,p2;
        if(y!=m){
            //p记录原来的位置,然后放入modify中删掉
            p1 = query(root[x],1,m+q,y,cur,0);
            p2 = query(root[n+1],1,n+q,x,res,1);
            modify(root[x],1,m+q,m+used[x]++,res,1);
            modify(root[n+1],1,n+q,n+i,cur,1);
            modify(root[x],1,m+q,p1,0,0);
            modify(root[n+1],1,n+q,p2,0,0);
        }else{
            p1 = query(root[n+1],1,n+q,x,cur,1);
            modify(root[n+1],1,n+q,p1,0,0);
            modify(root[n+1],1,n+q,n+i,cur,1);
        }
        printf("%lld\n",cur);
    }
}
View Code

 

六十七:选择客栈【组合数学】

 我们枚举所有price[i]小于P的位置,然后进行O(100)的组合数学暴力求和。

然后洛谷第一篇题解的O(n)做法就很秀了!~

const int maxn = 2e5+11,inf = 0x3f3f3f3f;
int n,m,pr,p[maxn],id[maxn];
ll cnt[111],sum[111],used[111],ans;

inline void solve()
{
    read(n,m,pr);
    for(re int i = 1;i <= n;i++){
        read(id[i],p[i]);
        sum[id[i]]++;
    }
    for(re int i = 1;i <= n;++i){
        ++cnt[id[i]];//一定要先加!!
        if(p[i]<=pr){
            for(re int j = 0;j <= 100;++j){
                //特判当前点的色调!!!
                ans += (cnt[j]-(id[i]==j))*(sum[j]-cnt[j]-used[j]);
                if(j==id[i]){
                    ans += sum[j]-used[j]-1;
                }
                used[j] += cnt[j];
                cnt[j] = 0;
            }
        }
    }
    printf("%lld\n",ans);
}
View Code

 

六十八:篝火晚会【环的知识+思维+贪心】

①结论一:如果能形成一个环,贪心地组环就可以了。

②首先需要贪心地想到,如果需要代价最小,我们需要找到【原始环】与【目标环】匹配点最多的位置。因为我们总有方法使得已经匹配的点不动,然后只动那些未匹配的点【所以结论二:answer = n - 已匹配点】

③要使得匹配点最多,咱们就需要用到两个结论:

  • 【1】如果两个环能通过左右移动来使得它们完全一致,那么环Ⅰ 映射到 环Ⅱ 上的点的距离必须一致。                             即:| ai - bi | = const_number 这个值就是需要左右移动的距离
  • 【2】如果两个环能通过反转+移动使得他们完全一致,那么环Ⅰ上点的位置 + 环Ⅱ上对应点的位置 = 一个常数。

               即 ( ai + bi )%n = const_number 

 所以只要(1)建环 (2)两次求最大值 就可以得到答案了。

 

六十九:宝藏【状压DP / dfs搜索】

①DP:

小技巧:假设s是一个利用二进制表示的集合,那么如何枚举它的所有子集呢?

for(int i=s;i;i=(i-1)&s)

②dfs

 

七十:Placing Medals on a Binary Tree(CF)

 ①把问题转换成1-  1/2 - 1/4 .... 直到减不动了,那么这个点就加不进去

 ②但是又出现了一个新的问题,浮点数精度不够,直接模拟高精的话又会TLE

 ③考虑到是一个完全二叉树的图形,我们把问题转换到二进制上面,变成【使用map】模拟二进制

然后如果一个深度存在两个节点,那么就进位到上一个节点。

 ④如果之前1、2、3、4、5 都出现过了,或者因为进位而出现了,那么我们再加一个5进去,这一颗树就满了【全部进位到0节点】,但是如果1、2、3、4、5都出现过,还出现过1000,那么5也就塞不进去了【也就是说,当进位到0的情况下,5应当是最大值】

  根据这个特殊的情况,我们定义 mx 为从 1 ~ mx 连续出现的序列【如1、2、3、4、5、6,mx为 6】

分类如下:

【1】显然,当进位到0、或者当前要放的medals的值小于mx 都是无法放入的,直接输出No

【2】当mx == medals 时,判断一下mx是不是等于maxx【之前的最大值】

【3】否则必然可以输出Yes

最后 ①更新一下 maxx,mx的   ②进位操作 就可以了

ll n,a,flag,mx,maxx;
unordered_map<int,int>has;
inline void solve()
{
    read(n);
    for(int i = 1;i <= n;i++)
    {
        read(a);
        maxx = max(maxx,a);
        if(has[0] || mx > a || a == 0){
            puts("No");
            continue;
        }
        if(mx == a && maxx > a){
            puts("No");
            continue;
        }
        else{
            has[a]++;
            while(a > 0){
                if(has[a] == 2){
                    has[a] = 0;
                    has.erase(has.find(a));
                    has[--a]++;
                }
                else break;
            }
            while(has[mx+1])++mx;
            puts("Yes");
        }
    }
}
View Code

 

七十一:Hidden Anagrams(CF)

。。。挺巧妙的一个优化 从O(n^3) 降到 O(n^2*logn)

还记得之前的等式对碰嘛?【x+y=0  只需要枚举x,然后用y去对碰,从而使O(n^2)降到O(n)】

这一题也是一样,我们使用一个map标记一下子串,然后用第二个字符串去对碰一下。

【这里hash_map装的是一个struct,表示前缀和差分之后的子串字母的数量,然后写一个operator<就可以实现logn的查找了】

char s1[maxn],s2[maxn];
struct node{
    int a[30];
    node(){me(a,0);};
    bool operator<(const node&t)const{
        for(int i = 0;i < 26;i++){
            if(a[i] < t.a[i])return true;
            if(a[i] > t.a[i])return false;
        }
    }
};
map<node,bool>has;
int n,m,lim,ans;
inline void solve()
{
    scanf("%s%s",s1+1,s2+1);
    n = strlen(s1+1),m = strlen(s2+1),lim = min(n,m);

    for(int i = 1;i <= lim;i++){
        has.clear();
        node tmp,tmp2;
        for(int j = 1;j <= n;j++){
            if(j < i){
                tmp.a[s1[j]-'a']++;
            }else{
                tmp.a[s1[j]-'a']++;
                if(j>i)tmp.a[s1[j-i]-'a']--;
                has[tmp] = 1;
            }
        }
        for(int j = 1;j <= m;j++){
            if(j < i){
                tmp2.a[s2[j]-'a']++;
            }else{
                tmp2.a[s2[j]-'a']++;
                if(j>i)tmp2.a[s2[j-i]-'a']--;
                if(1 == has[tmp2]){
                    ans = i;
                    break;
                }
            }
        }
    }
    printf("%d\n",ans);
}
View Code

 

七十二:H - Animal Companion in Maze(CF)

这个题哭了【调了一天】

思路:

① 本图有【有向边、无向边】两种边,之前做过一道使用分层图做的,但是这道题求最长路径,不能这样搞

② 然后首先解决一个问题,如果不存在Infinite的情况,那么这个图就可以视为一个森林:

    双向边连通块是树,然后 单向边是连着这些树的通道

所以我们就需要把树给分类出来,怎么分呢?树是双向边,使用tarjan缩点就可以给每个树一个编号啦!【之前以为tarjan只能缩环,没想到在DAG+树的图中,还能把树给抽出来】

③那么tarjan缩点之后呢?我们知道 环 对 tarjan缩点是没有影响的,所以一开始直接tarjan缩点就可以了,然后来一个check_circle函数,因为环在tarjan中已经缩掉了,所以直接从上面得到的环开始搜索就可以了。

如何判断有无环【前提是,遍历时只能遍历属于同一个连通块的点】:

  显然,如果之前走过的点又被遍历到,那么就是有环【不走父节点fa!=v的情况下】

  如: 2 2   1 2 2   1 2 2 【test①】

④判环完成之后,rebuild一下这个图,常规操作了,不过需要记录这条边的左右两个节点,方便后续DP【此时这些边一定是单向边】

⑤DP环节:

我们定义一个状态:dp[ i ]表示以 i 节点结尾的路径最长有多长。

【1】首先需要【拓扑排序】地遍历每一个缩点,这样我们就能保证所有进入这个缩点的dp[ i ]数组的值都是更新完毕的,这时候,我们就可以对这个联通缩点进行两次DFS

【2】第一次DFS,像求树上直径那样,求一个最深路径、次深路径

【3】第二次DFS,根据上述的fir、sec数组【从上往下】地更新dp数组,转移方程如下:【i是该节点,fa是父节点】

if(fir[fa] == fir[i] + 1)
    dp[i] = max({sec[i]+1,in[fa]+1,dis+1});
else
    dp[i] = max({fir[i]+1,in[fa]+1,dis+1});
    
    ①dis 是从上面往下累计的最大值
    ②in 数组是在BFS时记录的该节点最长【进入路径】
    如: 3 -> 2 -> 1 那么in[1] = 2,in[2] = 1

其实解决本题的关键就三个:

①想出森林+有向边相连的模型->②知道缩点【缩点取树】并且会判环->③会定义DP状态

const ll maxn = 1e5+11;
int n,m,top,dfn[maxn],ins[maxn],ind,flag,head[maxn],root[maxn];
int fir[maxn],sec[maxn],du[maxn],low[maxn],id[maxn],scc,h2[maxn];
int in[maxn],ans;
stack<int>s;
struct scc_edge{int to,a,b,next;}fe[maxn<<2];
struct edge{int to,i,next;}e[maxn<<2];
inline void add(int x,int y,int i){
    e[++top] = {y,i,head[x]};
    head[x] = top;
}
inline void add(int x,int y,int a,int b){
    fe[++top] = {y,a,b,h2[x]};
    h2[x] = top;
}

inline void check_circle(int x,int fa)
{
    if(low[x]){flag = true;return ;}
    low[x] = 1;
    for(int i = head[x],v;i;i=e[i].next)
    if(id[e[i].to]==id[x]){
        v = e[i].to;
        if(fa!=v)check_circle(v,x);
    }
}

inline void dfs(int x){
    s.push(x);
    ins[x]=dfn[x]=low[x]=++ind;
    for(int i = head[x];i;i=e[i].next){
        int v = e[i].to;
        if(!dfn[v]){
            dfs(v);
            low[x] = min(low[x],low[v]);
        }else if(ins[v]){
            low[x] = min(low[x],dfn[v]);
        }
    }
    if(low[x]==dfn[x]){
        scc++;int v;
        root[scc] = x;
        do{
            v = s.top();s.pop();
            ins[v] = 0;
            id[v] = scc;
        }while(x!=v);
    }
}

inline int dfs_1(int x)
{
    if(dfn[x])return fir[x];
    dfn[x] = 1;fir[x] = max(fir[x],in[x]);
    for(int i = head[x],v;i;i=e[i].next)
    if(!dfn[e[i].to] && id[x] == id[e[i].to]){
        v = e[i].to;
        int d = dfs_1(v) + 1;
        if(d > fir[x]){
            sec[x] = fir[x];
            fir[x] = d;
        }else sec[x]=max(sec[x],d);
    }
    //不需要ans=max(sec[x]+fir[x],ans);以防如下数据
    //那么树上直径就是答案怎么办?
    //哈哈,不怕,dp时可以解决
    /* 4 5
       1 2 1
       1 3 1
       1 4 1
       2 3 2
       4 3 2
    */
    return fir[x];
}

inline void dfs_2(int x,int dis)
{
    if(low[x])return ;
    low[x] = 1;
    for(int i = head[x],v;i;i=e[i].next)
    if(!low[e[i].to]&&id[x]==id[e[i].to]){
        v = e[i].to;
        if(fir[v]==fir[x]-1){
            dfs_2(v,max({dis+1,sec[x]+1,in[x]+1}));
        }else{
            dfs_2(v,max({dis+1,fir[x]+1,in[x]+1}));
        }
    }
    in[x] = max({in[x],fir[x],dis});//这里为了省内存,直接把in代替dp数组
}

inline void solve()
{
    read(n,m);
    for(int i = 1,t1,t2,op;i <= m;i++){
        read(t1,t2,op);
        add(t1,t2,i);
        if(op==2)add(t2,t1,i);
    }
    me(ins,0),me(low,0);
    for(int i = 1;i <= n;i++)if(!dfn[i])dfs(i);

    //check_circle
    me(ins,0),me(low,0);
    for(int i = 1;i <= scc;i++)if(!low[i]){
        check_circle(i,0);
        if(flag){
            puts("Infinite");
            return ;
        }
    }

    //rebuild
    top = 0;
    for(int x = 1;x <= n;x++){
        for(int i = head[x],v;i;i=e[i].next){
            v = e[i].to;
            if(id[v] != id[x])
                add(id[x],id[v],x,v),du[id[v]]++;
        }
    }
    me(dfn,0),me(low,0);

    //BFS过程
    queue<int>q;
    for(int i = 1;i <= scc;i++)
        if(!du[i])q.push(i),dfs_1(root[i]),dfs_2(root[i],0);
    while(!q.empty()){
        int x = q.front();q.pop();
        for(int i = h2[x],v;i;i=fe[i].next){
            v = fe[i].to;
            du[v]--;
            in[fe[i].b] = max(in[fe[i].b],in[fe[i].a]+1);
            if(du[v]==0){
                    //开始两次DFS
                q.push(v);
                dfs_1(root[v]);
                dfs_2(root[v],0);
            }
        }
    }
    for(int i = 1;i <= n;i++)
        ans = max(ans,in[i]);//,de(in[i]);
    printf("%d\n",ans);
}
View Code

 

七十三:Symmetric Matrix【牛客】

一道邻接矩阵的构造题+组合数DP+数学推算化简公式

总结:

①矩阵上特殊的值,看看能不能视为邻接矩阵,本题中一行 有两个1 / 一个2,而且还是对称矩阵,说明A有一条指向B的边,B一定有一条指向A的边,所以2个点的环、两个以上点的环都可以表示出来。

②圆排列:k个节点形成的环有多少种情况? answer = (k-1)! / 2 

由于我们选取k个数与第n个节点形成环的话,又存在多种情况,根据圆排列,一共有k+1个节点,所以是 k!/ 2 种情况

 【题解

 

七十四:小G的GCD【牛客】思维题

最终状态是:(1,0)时最优,然后倒数第二步是:(2,1)时最优,为了使得辗转相除的次数最多,当我们得到一个状态(x,y)时,我们可以构造出(y+x,x)是它的上一步状态,然后(x*2+y,y+x),再然后是(x*3+y*2,x*2+y)

即答案最大时,(A,B)的上一步应该是(B+A,A)

得到:

[ f(x+1) ,f(x) ]  的前一个状态是 [ f(x+1)+f(x), f(x+1) ] 令f(x+2) = f(x+1) + f(x)

咦?这不是斐波那契数列嘛? 所以我们求斐波那契数列中小于n的最大一项最好了!

const int maxn = 111,inf = 0x3f3f3f3f,mod = 1e9+7;
ull n,sum,a[maxn];
//gcd 最大沿着类似log2的方式递增,所以111够了

inline void solve()
{
    read(n);
    if(n==1){//特判
        printf("2\n");
        return ;
    }
    a[0] = a[1] = 1;
    for(ull i = 2;i;i++){
        a[i] = a[i-1] + a[i-2];
        if(a[i] > n){
            printf("%d\n",i);
            return ;
        }
    }
}
View Code

 

七十五:Simone and Graph Coloring【2021昆明站ICPC】

这道题手糊一下发现需要的颜色就是最长递减子序列,然后lower_bound维护一下就好了

但是还要输出每个节点的颜色,这该怎么办?

我们发现,每一个节点都可以存在一个递减序列里面【任意一个即可】,而这个递减序列的长度必然小于等于max_length【最长的递减子序列的长度】

所以这个节点的颜色编号只需要贪心地在【离他最近的、在他前面的、ai 大于他的】节点的颜色上加一即可。

比如数据:7 , 7 3 2 6 5 1 4 

 形成的最长递减子序列一定相互有边,所以直接互不相同。

然后这样想:如果在第i个数之前有比 ai 大的数,那么我们就可以构建出包含 ai ,并且以 ai 结尾的最长递减子序列,这个时候,我们把这个子序列的倒数第二项作为 ai 的 father 即可,输出的时候在 col[ father ] 基础上直接加一。 

【橙色部分很重要,是贪心的关键思想,至于怎么实现呢?我们在维护最长子序列的过程中顺便更新一下就好】

const int maxn = 1e6+11,inf = 0x3f3f3f3f,mod = 1e9+7;
int n,a[maxn],ans,fa[maxn],col[maxn],mn[maxn],top;

inline void solve()
{
    read(n);
    for(int i = 1;i <= n;i++){
        read(a[i]);
        col[i] = fa[i] = 0;
    }
    top = 0;
    for(int i = 1;i <= n;i++){
        if(top==0||mn[top]>a[i]){
            if(top)fa[a[i]] = mn[top];
            mn[++top] = a[i];
        }else{
            int pos = lower_bound(mn+1,mn+1+top,a[i],greater<int>()) - mn;
            if(pos>1&&mn[pos-1]<a[i])pos--;
            mn[pos] = a[i];
            if(pos > 0){
                fa[a[i]] = mn[pos-1];
            }
        }
        //for(int j = 1;j <= top;j++){
        //    cout<<mn[j]<<" ";
        //}pln;
    }
    ans = top;
    for(int i = 1;i <= n;i++){
        de(a[i]<<" "<<fa[a[i]]);
        col[a[i]] = (col[fa[a[i]]])%ans + 1;
    }
    for(int i = 1;i <= n;i++){
        printf("%d ",col[a[i]]);
    }pln;
}

int main()
{
    //freopen("P5022_23.in","r",stdin);
    int TEST = 1;
    //cin>>TEST;
    ~scanf("%d",&TEST);
    //getchar();
    while(TEST--)
        solve();
}
View Code

 

七十六:对称二叉树【牛客】暴力搜索、递归妙用

首先题目给的是一颗二叉树,但是完全有可能退化成两条对称的链,所以我们没有办法将高度降到logn级别

然后只能在这棵树上搜索了:

  (1)题目要求每一棵子树是从根到它的所有子节点,也就是说,如果从上往下搜索时,出现了一颗对称的,那么就不需要再往下搜索了。所以直接将size大的排序,从上往下贪心地搜【优化一】

  (2)然后关于暴力搜索的设计:【u是root的左节点,v是root的右节点】

  check(son[u][0],son[v][1]) && check(son[u][1],son[v][0]) 左和右同时进行对称check即可

我也不知道为什么这个复杂度过得去(苦笑),反正我算出来【极端情况下】会被卡。

int n,son[1000050][2],val[1000050],size[1000050];
int id[1000011];
//son[i][0]为i的左儿子
//son[i][1]为i的右儿子
 
inline void dfs(int u)
{
    size[u]=1;
    if (son[u][0]!=-1)
    {
        dfs(son[u][0]);
        size[u]+=size[son[u][0]];
    }
    if (son[u][1]!=-1)
    {
        dfs(son[u][1]);
        size[u]+=size[son[u][1]];
    }
}
 
inline bool check(int u,int v)
{
    if (u==-1 && v==-1)
        return true;
    if (u!=-1 && v!=-1 && val[u]==val[v] && check(son[u][0],son[v][1]) && check(son[u][1],son[v][0]))
        return true;
    return false;
}
 
bool cmp(int x,int y){
    return size[x] > size[y];
}
 
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
        val[i]=read(),id[i]=i;
    for (int i=1;i<=n;i++)
    {
        son[i][0]=read();
        son[i][1]=read();
    }
    dfs(1);
    sort(id+1,id+1+n,cmp);
    int ans=0;
    for (int i=1;i<=n;i++){
        int ti = id[i];
        if (check(son[ti][0],son[ti][1])){
            ans=max(ans,size[ti]);
            break;
        }
    }
    cout << ans << endl;
    return 0;
}
View Code

我觉得最重要的是:①有递归的想法②大胆去尝试

 

 

七十七:最小生成树【模板与教训】

经过这一道题之后,我深刻地明白:如果要使用【最小生成树】算法/思想,必须要严格按照边权从小到大加起来【或者从大到小】

我一开始想着.... 不排序,从小的点开始计算

 

七十八:Gentle Jena【单调栈+DP】

 说实话,DP的题目有灵感就好做了

#include <stdio.h>
#include <iostream>
#include <ctime>
#include <iomanip>
#include <cstring>
#include <algorithm>
#include <queue>
#include <bitset>
//#include <chrono>
//#include <random>
//#include <unordered_map>
//#pragma GCC optimize(3,"inline","Ofast")
#include <cmath> // cmath里面有y1变量,所以外面不能再声明
#include <assert.h>
#include <map>
#include <set>
#include <stack>
#define lowbit(x) (x&(-x))//~为按位取反再加1
//#define re register
#define mseg ((l+r)>>1)
#define ls (ro<<1)
#define rs ((ro<<1)|1)
#define ll long long
#define lld long double
#define uint unsigned int
#define ull unsigned long long
#define fi first
#define se second
#define pln puts("")
#define deline cout<<"-----------------------------------------"<<endl
#define de(a)  cout<<#a<<" = "<<a<<endl
#define de2(a,b) de(a),de(b),cerr<<"----------"<<endl
#define de3(a,b,c) de(a),de(b),de(c),cerr<<"-----------"<<endl
#define de4(a,b,c,d) de(a),de(b),de(c),de(d),cerr<<"-----------"<<endl
#define emp(a)  emplace_back(a)
#define iter(c)   __typeof((c).begin())
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define PII pair<int,int>
#define PLL pair<ll,ll>
#define me(x,y) memset((x),(y),sizeof(x))
#define mp make_pair
using namespace std;
//template<class T>inline void fill(T*p,T v,re int n){while(n--)p[n]=v;}
template<class T,class F>inline F max(T a,F b){return (a>b)?a:b;}
template<class T,class F>inline F min(T a,F b){return (a>b)?b:a;}
template<typename T>
inline void read(T&res){ //
    ll x=0,f=1;char ch;
    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();}
    res = x*f;
}
template<typename T,typename...Args>inline
void read(T&t,Args&...a){read(t);read(a...);}


const int maxn = 1e7+11,inf = 0x3f3f3f3f,mod = 998244353;
ll n,m,b,ans,x,y,z,p,A[maxn],L[maxn],top;
PLL s[maxn];

inline void update(ll x,ll p)
{
    while(top>0&&s[top].fi>=x)top--;
    if(top>0)
        L[p] = (L[s[top].se]+(p-s[top].se)*x%mod)%mod;
    else
        L[p] = x*p%mod;
    s[++top] = mp(x,p);
}

inline void solve()
{
    read(n,p,x,y,z,b);
    L[1] = A[1] = b;
    s[++top] = mp(b,1);
    for(int i = 2;i <= n;i++){
        b = (A[i-1]*x%p+b*y%p+z)%p;
        update(b,i);
        A[i] = (L[i] + A[i-1])%mod;
    }
    for(int i= 1;i <= n;i++){
        ans ^= A[i];
    }
    printf("%lld\n",ans);
}

int main()
{
    //freopen("P5022_23.in","r",stdin);
    int TEST = 1;
    //cin>>TEST;
    //~scanf("%d",&TEST);
    //getchar();
    while(TEST--)
        solve();
}
View Code

 

七十九:Lottery Tickets【贪心+构造】

该题难在如何构造后面两位数,使得整个串最大!【反正就是细节一大堆wdnmd】

贪心的思路:【我们到底如何做,才能构造出正确的最后两位呢?】

{0,20,12,32,40,4,24,44,52,60,16,36,64,56,72,76,80,8,28,84,48,68,88,92,96} 按照这个数组去查找就行了【真的!!】

(1) 比较 个位十位 中最大值的大小 【这一点很重要,决定了小的数先出场,保证大的数不会浪费在最后两位】

(2) 如果相同,就比较个位的大小  【这一点也很重要,保证了所有合法的串中,最后一位是最小的!】

(3)如果还相同,就比较十位大小。

该数组是这样得来的:然后这样找就能保证找到之后,整个串还是最大的!! 

int a[20],suf1,suf2;

inline bool ok(){
    int ans = -1;
    for(int i = 0;i < 10;i++){
        if(a[i]>0)
        for(int j = 0;j <= i;j++){
            if(a[j]==0)continue;
            if(i==j&&a[i]<2)continue;
            if((i*10+j)%4==0){
                a[j]--,a[i]--;
                suf1 = i,suf2 = j;
                return true;
            }
            if((j*10+i)%4==0){
                a[j]--,a[i]--;
                suf1 = j,suf2 = i;
                return true;
            }
        }
    }
    for(int i = 0;i < 10;i+=4)
        if(a[i])suf1 = i;
    return false;
}

inline void solve()
{
    me(a,0);
    bool flag = 0;
    for(int i = 0;i <= 9;i++)
    {
        read(a[i]);
        if(i&&a[i])flag=1;
    }
    suf1=suf2=-1;

    if(!flag){
        if(a[0])puts("0");
        else puts("-1");
        return ;
    }

    if(!ok()){
        printf("%d\n",suf1);
    }else{
        for(int i = 9;i >= 0;i--){
            while(a[i]>0){
                printf("%d",i);
                a[i]--;
            }
        }
        printf("%d%d\n",suf1,suf2);
    }
}
View Code

 

八十:2020-2021 ICPC - Gran Premio de Mexico - Repechaje - I. Integers Rectangle Challenge 【CF gym】

①首先想最小子矩阵怎么做:

我们枚举 : (a,b,c,d)为左/右边界点,这样可以吗?的确可以,但是怎么转移?

我们换个方向 : (h,w,i,j)   

  • 以(i-h+1)为上边界,以i为下边界
  • 以(j-w+1)为左边界,以j为右边界

然后,将 h,w 从小枚举到大,这样转移:

    //a b c d 最大子矩阵
    //me(mx,-inf);//可有可无
    for(int h = 1;h <= n;h++)
     for(int w = 1;w <= m;w++)
      for(int i = h;i <= n;i++)
       for(int j = w;j <= m;j++){
        mx[h][w][i][j] = getSum(i-h+1,j-w+1,i,j);
        if(h > 1){
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h-1][w][i][j]);
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h-1][w][i-1][j]);
        }
        if(w > 1){
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h][w-1][i][j]);
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h][w-1][i][j-1]);
        }
    }

②这道题基本上就做好了,剩下的就是如何划分的问题了,划分方式有6种,枚举即可。【这六种划分看看代码就行】

const ll maxn = 55,inf = 0x3f3f3f3f3f,mod = 998244353;
ll sum[maxn][maxn],n,m,mx[maxn][maxn][maxn][maxn],ans;

inline ll getSum(int a,int b,int c,int d){
    return sum[c][d]-sum[a-1][d]-sum[c][b-1]+sum[a-1][b-1];
}

inline ll getMax(int a,int b,int c,int d){
    return mx[c-a+1][d-b+1][c][d];
}

inline void init(){
    //二位前缀胡
    for(int i = 1;i <= n;i++)
      for(int j = 1;j <= m;j++)
        sum[i][j] += sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];

    //a b c d 最大子矩阵
    //me(mx,-inf);//可有可无
    for(int h = 1;h <= n;h++)
     for(int w = 1;w <= m;w++)
      for(int i = h;i <= n;i++)
       for(int j = w;j <= m;j++){
        mx[h][w][i][j] = getSum(i-h+1,j-w+1,i,j);
        if(h > 1){
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h-1][w][i][j]);
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h-1][w][i-1][j]);
        }
        if(w > 1){
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h][w-1][i][j]);
            mx[h][w][i][j] = max(mx[h][w][i][j],mx[h][w-1][i][j-1]);
        }
    }
}

inline ll slice_X(int L,int R){
    ll mx = -inf;
    for(int i = 1;i < m;i++)
        mx = max(mx,getMax(L,1,R,i)+getMax(L,i+1,R,m));
    return mx;
}

inline ll slice_Y(int L,int R){
    ll mx = -inf;
    for(int i = 1;i < n;i++)
        mx = max(mx,getMax(1,L,i,R)+getMax(i+1,L,n,R));
    return mx;
}

inline void solve()
{
    ans = -inf;
    read(n,m);
    for(int i = 1;i <= n;i++){
      for(int j = 1;j <= m;j++){
          read(sum[i][j]);
        }
    }
    init();
    for(int i = 1;i < n;i++){
        ans = max(ans,slice_X(1,i)+getMax(i+1,1,n,m));
        ans = max(ans,slice_X(i+1,n)+getMax(1,1,i,m));
        for(int j = i+1;j < n;j++){
            ans = max(ans,getMax(1,1,i,m)+getMax(i+1,1,j,m)+getMax(j+1,1,n,m));
        }
    }
    for(int i = 1;i < m;i++){
        ans = max(ans,slice_Y(1,i)+getMax(1,i+1,n,m));
        ans = max(ans,slice_Y(i+1,m)+getMax(1,1,n,i));
        for(int j = i+1;j < m;j++){
            ans = max(ans,getMax(1,1,n,i)+getMax(1,i+1,n,j)+getMax(1,j+1,n,m));
        }
    }
    printf("%lld\n",ans);
}
View Code

 

八十一:Newest Jaime's Delivery【CF gym】                                                                                                                              bit_masking+二分答案+最短路dijkstra验证。                                                                                                                                                                                                                                                                                                                                             

八十二:求长度为n 而且 a!= i 的序列个数 【DP / 容斥 / 错位排列/ 错排】                                                                                                              首先一个序列不对应位置相等,有两种来源:

【1】如果n-1序列不对应位置,那么我在序列末尾加一个,然后拿末尾的数去换,那么一定能得到一个n的不对应序列

比如说: 3 1 2 ,我加一个4  变成 : 3 1 2 4 ,如果4和1,2,3互换的话,就能得到3个合法序列

【2】如果本来那个序列有一个位置是对应的,剩下n-2是不对应的

比如说 3 2 1 中 2是对应的,我加入一个4,4和2换了,3 4 1 2就是符合条件的序列

const ll maxn = 1e6+11,inf = 0x3f3f3f3f3f,mod = 1e9+7;
ll dp[maxn],n;

inline void solve()
{
    read(n);
    dp[1] = 0,dp[2] = 1,dp[3] = 2;
    for(int i = 4;i <= n;i++){
        dp[i] = (dp[i-1]*(i-1)%mod + (i-1)*dp[i-2]%mod)%mod;
    }
    printf("%lld\n",dp[n]);
}
View Code

 

 八十三:树上DP【洛谷】或者使用【点分治】

 只能说代码很难调。

一般来说,树上的枚举两个节点,然后对答案有贡献,不妨考虑树上DP/点分治

但是如果每个点的贡献次数不一样,那么可能就只能用点分治了,看下面的例题。

//树上DP的细节太多了。。。
const ll maxn = 5e4+11,inf = 0x3f3f3f3f3f,mod = 998244353;
ll f[maxn][3],n,m,sm[maxn][3],L,R;
vector<PII>e[maxn];

inline void dfs_cnt(int x,int fa){
    //定义sm[x][i] 为以x为终点的路径数量,i为长度%3
    L += sm[x][0];
    for(int i = 0;i < 3;i++)
        R += sm[x][i];
    for(auto [v,l] : e[x])
        if(v!=fa)dfs_cnt(v,x);
}

inline void dfs_pre(int x,int fa){
    //由于题目可以存在自边,所以一开始直接赋值为1
    f[x][0] = 1;
    for(auto [v,l] : e[x]){
        if(v!=fa){
            dfs_pre(v,x);
            //从子节点那里转移
            //由于子节点f[v][0] = 1了,所以不需要f[x][l]++;
            for(int i = 0;i < 3;i++){
                f[x][(i+l+9)%3] += f[v][i];
            }
        }
    }
}

inline void dfs_sum(int x,int fa,vector<int>vec){
    //定义vec数组为从上往下的路径数量
    //定义f为从下往上
    for(int i = 0;i < 3;i++)
        sm[x][i] = f[x][i] + vec[i];

    vector<int>tmp(3,0);
    for(auto [v,l] : e[x]){
        if(v!=fa){
            //定义:i为到x距离为i的路径数量
            //那么tmp里面就要i+L
            //那么f[v][i-l] 要减去那些以v节点为根的子树边数量
            for(int i = 0;i < 3;i++)
            tmp[(i+l)%3] = sm[x][i] - f[v][(i-l+9)%3];
            dfs_sum(v,x,tmp);
        }
    }
}

inline void solve()
{
    read(n);
    for(int i = 1,t1,t2,t3;i < n;i++){
        read(t1,t2,t3);
        t3 %= 3;
        e[t1].emp(mp(t2,t3));
        e[t2].emp(mp(t1,t3));
    }
    dfs_pre(1,0);
    dfs_sum(1,0,vector<int>(3,0));
    dfs_cnt(1,0);
    ll g = __gcd(L,R);
    L /= g,R /= g;
    printf("%lld/%lld\n",L,R);
}
View Code

 

 八十四:小Q 与 树 【点分治 / 树链剖分HLD】

 点分治+枚举LCA 【如果使用了点分治,可能还要容斥一下,也很快的!】

const ll maxn = 2e5+11,inf = 0x3f3f3f3f3f,mod = 998244353;
ll n,a[maxn],ans,sz[maxn],son[maxn];
vector<int>e[maxn];
vector<PLL>vec;
bool vis[maxn];


// sz 累计子树的节点数量
// son指的是重儿子的子树节点数【sz最大的儿子--重儿子】
// rt 为重心,重儿子最小的节点【任意一个即可】

vis[0] = son[0] = sz[0] = inf;//!!!!!!初始化操作
inline int root(int x,int fa,const int&tot){
    sz[x] = 1,son[x] = 0;
    int rt=0,tmp=0;
    for(int v : e[x]){
        if(v == fa || vis[v])continue;
        tmp = root(v,x,tot);
        sz[x] += sz[v]; //
        son[x] = max(son[x],sz[v]); //
        if(son[rt] > son[tmp])
            rt = tmp;
    }
    son[x] = max(son[x],tot-sz[x]);
    if(son[x] < son[rt])
        rt = x;
    return rt;
}

//获取以rt为根的树的节点深度,点权值
inline void get(int x,int dep,int fa){
    vec.emp(mp(a[x],dep));
    for(int v : e[x]){
        if(v == fa || vis[v])continue;
        get(v,dep+1,x);
    }
}

//计算
inline void work(int x,int dep,const ll&sig){
    vec.clear();
    get(x,dep,0);
    ll tmp = 0,s1 = 0,s2 = 0;
    sort(vec.begin(),vec.end());
    for(auto [v,d] : vec){
        tmp = (tmp + s2 + s1*d%mod)%mod;
        s1 = (s1+v)%mod;
        s2 = (s2+d*v)%mod;
    }
    ans = ((ans+sig*tmp)%mod+mod)%mod;
}

inline void dfs(int x){
    vis[x] = 1;
    work(x,0,1);//以x为重心点记录路径数量十分有效
    for(int v : e[x]){
        if(vis[v])continue;
        work(v,1,-1); //由于同一颗树的计算还不是最优的,所以减去
        dfs(root(v,0,sz[v]));
    }
}

inline void solve()
{
    read(n);
    for(int i = 1;i <= n;i++)read(a[i]),e[i].clear();
    for(int i = 1,t1,t2;i < n;i++){
        read(t1,t2);
        e[t1].emp(t2);
        e[t2].emp(t1);
    }

    dfs(root(1,0,n));
    printf("%lld\n",ans*2%mod);//答案乘二即可
}
View Code

 

八十五:点分治的层数【小吨的点分治  --- 牛客】

 

八十六:给出日期,求出n天之后的日期(n可以很大)DATE模板 基础数论

大佬教程】但我不打算精通蔡勒了。

namespace MY_DATE{
    //用于跳转的常量
    const string week[8]={
        "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"
    };
    const int year_400  = 1460097;
    const int m_day[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
    //辅助函数
    bool isLeap(int t){return(t%400==0)||((t%4==0)&&(t%100));}
    /**year_1[pick(y,m)]*/
    int dayThisMonth(int y,int m){return m_day[m]+(m==2&&isLeap(y));}

    //类的定义
    class my_DATE{
    public:
        int year,month,day;
        my_DATE(int y=2020,int m=1,int d=1):year(y),month(m),day(d){};
        bool operator<(const my_DATE&a)const{
            return year != a.year ? year < a. year :
                month != a.month ? month < a.month : day < a.day;
        }
        bool operator==(const my_DATE&a)const{
            return year == a.year && month == a.month && day == a.day;
        }
        bool operator>(const my_DATE&a)const{return !((*this<a)|(*this==a));}
        my_DATE next_K_Days(int k){
            k += this->getCalc();
            int x = k + 1789995, n, i, j, y, m, d;
            n = 4 * x / 146097;
            x -= (146097 * n + 3) / 4;
            i = (4000 * (x + 1)) / 1461001;
            x -= 1461 * i / 4 - 31;
            j = 80 * x / 2447;
            d = x - 2447 * j / 80;//计算日期
            x = j / 11;
            m = j + 2 - 12 * x;//计算月份
            y = 100 * (n - 49) + i + x;//计算年份
            return my_DATE(y,m,d);
        }
        int getCalc(){//辅助计算函数,好像是蔡勒公式未化简形式【板子抄的.】
            int y = year , m = month , d = day;
            if(m <= 2) {y--; m += 12;}
            return 365*y + y/4 - y/100 + y/400 + (153*(m-3)+2)/5 + d - 307;
        }
        int daysHasPass()const{
            int y = year,m = month;
            if(y == 0 && m <= 2)return (m==2)*31 + day;
            if(m <= 2)
                //如果这一年是闰年,但是没有达到2月,所以不加1
                return y*365 + y/4 + y/400 - y/100 + (y>0&&!isLeap(y)) + (m==2)*31 + day;
            else
                return y*365 + y/4 + y/400 - y/100 + 60 + //60 = 59 + (y>=0)
                    (int)(13.0*(m-3)/5.0+0.5) + 28*(m-3) + day ;
        }
        string Zeller(){//蔡勒公式
            int c = year/100 , m = month, y = year , d = day;
            if(m <= 2){m+=12;y--;}// m 的取值为3~14
            y %= 100;    // y 取年份后两位
            int tmp = (c/4 + y + y/4 - 2*c + (13*(m+1)/5) + d - 1) % 7;
            //int tmp = (this->getCalc()%7+7)%7 + 1;//等价于zeller,不知道为什么。。
            return week[tmp];
        }
        friend int daysBetween(const my_DATE&A,const my_DATE&B){
            int f = 0;
            my_DATE Special(1582,10,4);
            if(A<Special&&Special<B || B<Special&&Special<A)f = 10;
            return abs(A.daysHasPass()-B.daysHasPass()) - f;
        }
    };
//该命名空间只在1582年10月15号之后的日期有效
}using namespace MY_DATE;
View Code

 

 

 

 

 

八十七:Complete the MST【最小生成树+并查集+思维+dfs超级减枝】

CF的出题人又教我做人了。

很容易想到设置一条边为异或和,其余为0,但是难的是其他部分啊。。【好吧,难的是那个dfs+并查集】

首先想一个问题,如果没有xor为0的约束,也就是没有设置的边都是 0,那该怎么办?

我们应该看看边权为0的边究竟能不能形成一棵树,否则,就拿已经设置好的边从小到大加进去(没有替换)【类似最小生成树】 

① 能不能形成一棵树,只需要看一个节点能不能伸出一条边连到主树就可以了,这一部分使用并查集+dfs维护即可。

但是这个dfs挺难的,n要达到2e5啊,怎么减枝呢?每一次我们都要选一条不存在的边,没有遍历过的节点,然后连边,也就是说,最优情况下每个节点只会被dfs一次,那么压力就降到选一条不存在的边上了。

题解使用了两次二分查找,使用set来存不在树上的节点。

inline void dfs(int x,int fa){
    s.erase(x);
    sort(to[x].begin(),to[x].end());
    for(int i = 1;i <= n;i++){
        auto it = s.lower_bound(i);
        if(it==s.end()){
            return ;
        }
        i = *it; //这一步是减枝的精髓
        if(s.count(i) && !binary_search(to[x].begin(),to[x].end(),i)){
            A.unite(x,i);
            tot--;//减去使用的不存在的边
            dfs(i,x);
        }
    }
}

因为set里面的元素一直在减少,但是一次dfs从1~n枚举是不变的,我们不能改变范围,所以我们直接给出一个范围,再二分出一个未被遍历的点,再看看边是不是不存在即可。

之前一直不知道怎么通过不存在的边来访问节点,现在会了。(x

②那么加上限制条件呢?

按照上面的步骤,我们极有可能使用了一条带边权的边(即异或和的边),那么怎么知道有没有用呢?

我们不是 tot-- 了嘛,如果我们 tot 用完了,也就是 tot <= 0,那么就代表我们用的边中,有一条是带边权的,我们现在的任务是揪出它。【这里选用贪心解决】

这条边的条件:不能是pre-assign的,在dfs完毕之后的树上。

题解中使用一个A并查集维护dfs完毕之后的树,然后再慢慢加入pre-assign的边,同时再用一个B并查集维护pre-assign形成的最小生成树,如果一条边的两个节点在同一颗B树上了,那么就没有必要再加进去替换(异或和那条边了,因为之前更小边权的边更有资格)详细再看代码把。【注意:B并查集的作用不能被取代,因为替换的边必须保证是在A中出现,在B中未出现的,”出现“指的是具有相同效果的边】

const int maxn = 2e5+11,inf = 0x3f3f3f3f;
ll n,m,ans,mx,sum,tot,ne;
vector<array<int,3> >e;
vector<int>to[maxn];
set<int>s;

struct dsu{
    int fa[maxn];
    void init(){
        for(int i = 1;i <= n;i++)
            fa[i] = i;
    }
    int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
    bool unite(int x,int y){
        x = Find(x),y = Find(y);
        if(x == y)return 0;
        fa[y] = x;return 1;
    }
}B,A;

inline void dfs(int x,int fa){
    s.erase(x);
    sort(to[x].begin(),to[x].end());
    for(int i = 1;i <= n;i++){
        auto it = s.lower_bound(i);
        if(it==s.end()){
            return ;
        }
        i = *it; //这一步是减枝的精髓
        if(s.count(i) && !binary_search(to[x].begin(),to[x].end(),i)){
            A.unite(x,i);
            tot--;
            dfs(i,x);
        }
    }
}

inline void solve()
{
    read(n,m);
    A.init(),B.init();
    for(int i = 1;i <= n;i++)
        s.insert(i);

    for(int t1,t2,t3,i = 1;i <= m;i++){
        read(t1,t2,t3);
        e.push_back({t3,t1,t2});
        to[t1].emp(t2);
        to[t2].emp(t1);
        sum ^= t3;
    }
    tot = (n-1)*n/2 - m;
    sort(e.begin(),e.end());
    for(int i = 1;i <= n;i++)
        if(s.count(i))dfs(i,0);

    if(tot > 0){
        sum = 0;
    }

    for(auto [val,x,y] : e){
        if(A.unite(x,y)){
            ans += val;
            B.unite(x,y);
        }
        else if(B.unite(x,y)){
            sum = min(sum,val);
        }
    }
    ans += sum;
    printf("%lld\n",ans);
}
View Code

 

八十八:给定一个数sum,求有多少组序列满足如下条件:

这个序列的元素之和为sum

这个序列的差分数组是递增的

 

八十九:E - Group Photo【双指针+思维】

这个要想到用双指针来计算真的很难,单调性虽然明显,但是还是不会。。

首先要想出计算方法:枚举L奇偶性不同时的四种情况,然后R指针指向临界的位置,再 (L+R)/2+1计算答案。

一定要先枚举L!!【惨痛教训】

const ll maxn = 2e5+11,inf = 0x3f3f3f3f,mod = 998244353;
ll n,a[maxn],ans,sum[maxn],b[maxn],pos;

inline ll work(int L,int R,ll ps)//ps :p的值之和
{
    /*
    双指针一定要先枚举L的位置,然后再调整R的位置
    这样既不会重复,也不会少算
    */
    //L :左边连续的C的最右边一个位置
    //R :右边连续的P的最左边的C的位置,比如说:CPCPPP
    //L就是1,R就是3
    if(L>R)return 0;
    if(R%2!=L%2)ps += a[R--];
    ps += b[R-1] - b[L-1];//计算出相间的P的和
    ll ans = 0;
    while(L<=R){
        while(L<=R&&(ps+a[R])*2<=sum[n])ps+=a[R],R-=2;//等号不能漏掉
        if(L<=R&&ps*2<=sum[n]&&(ps+a[R])*2>sum[n])ps+=a[R],R-=2;
        if(L<=R&&ps*2>sum[n])ans += ((R-L)>>1)+1;
        ps -= a[L+1];
        L += 2;
    }
    return ans;
}

inline void solve()
{
    read(n);

    ans = 0;
    for(int i = 1;i <= n;i++){
        read(a[i]);
        sum[i] = sum[i-1] + a[i];
    }
    if(n==1){
        return (void)puts("1");
    }

    b[1] = a[1];
    for(int i = 2;i <= n;i++){
        b[i] = b[i-2] + a[i];
    }

    //暴力计算
    for(int i = 1;i <= n;i++){
        if(sum[i]*2>sum[n])ans++;
    }

    if(n>2){
        /*
        每一种开头结尾都需要考虑到开头是C,还是CC
        也就是奇偶性的两种情况
        */
        ans += work(1,n-1,a[n]) + work(2,n-1,a[n]);//强制C,P
        ans += work(2,n-2,a[1]+a[n-1]) + work(3,n-2,a[1]+a[n-1]);//强制P,C
        ans += work(1,n-2,a[n-1]) + work(2,n-2,a[n-1]);//强制C,C
        ans += work(2,n-1,a[1]+a[n]) + work(3,n-1,a[1]+a[n]);//强制P,P
    }
    else ans += a[n] > a[1];//n=2时,特判一下就好

    printf("%lld\n",ans%mod);

}
View Code

 

九十:赚钱 【SPFA + 思维】

每个点带有点权,每条边带有负边权,求一条最大权值路径,当出现正权环时输出orz

这个题只需要想到添加一个0节点当作起点,跑最长路就可以了。

  •  SPFA 的特点在于可以判正/负环,但是会容易被卡时间
  • dijkstra适合正权图
  • Floyd就适合任意两点之间的最短路

此题仅当n比较小时才能这样做,否则被卡死,还是建议用强连通分量+树上DP来做

 思考题:那么要求单向边的图上单源最短环怎么求?把所有单向边分为正向跑一次dijkstra,再把所有边反向跑一次dijkstra即可

 

九十一:B - Balloon Warehouse 【递归思维】

从一开始插入数字难解决问题,我们来反向思考。

搜先离0最近的数字,一定是最后一组(0,y),然后对于每一个数字都是一样的,所以我们逆向递归。

样例 :0 1 2 1 2 3 ,(0,1)(1,3)(0,1)(1,2)

先从0开始,变成 : 0 1   

再从1拓展:0 1 2 

因为2后面没有数字了,返回到根节点 1,那么直接填数 0 1 2 3 吗? no。因为这个1出现的时候,3已经出现了,所以这个1与3没有关系,【记录一个 i 索引判断下一个节点的索引是不是大于本节点就行了】,直接返回到0节点。

然后0节点又遍历到(0,1),所以再添一个节点1,变成 0 1 2 1 ,然后这个1的索引又比2小,所以再加一个2,然后返回到1,这个1的索引比3要小,所以加一个3,最后返回到根节点0。

然后除此之外,如果填入的数字不够L大,那么就会出现循环节,取个模就行了【别忘了题目说,ans[0] = 0】

【由于题目说L,R小于1e6,所以数组开到1e6就可以了,复杂度在减枝之后是O(n)的】

const ll maxn = 1e6+11,inf = 0x3f3f3f3f3f,mod = 998244353;
int ans[maxn],top,n,L,R;
vector<PII>e[maxn];

inline void dfs(int x,int y,int i){
    if(top >= maxn || y < 0 || i > e[x][y].se)return ;//减枝,复杂度O(n)
    ans[top++] = e[x][y].fi;
    dfs(e[x][y].fi,e[ e[x][y].fi ].size()-1,e[x][y].se);
    dfs(x,y-1,i);
}

inline void solve()
{
    read(n,L,R);
    for(int t1,t2,i = 1;i <= n;i++){
        read(t1,t2);
        e[t1].emp(mp(t2,i));
    }
    top = 1;//因为ans[0] = 0
    dfs(0,e[0].size()-1,0);
    for(int i = L;i < R;i++){
        printf("%d ",ans[i%top]);
    }pln;
}
View Code

 

九十二~九十三:树上阶梯NIM博弈

(1)焦糖布丁

根据树上阶梯博弈,我们只要构造出一颗树,使得深度为奇数的节点的异或和为0即可,不妨考虑一颗3层的树,boss为0层,其余为1,2层,那么怎么知道能否构造异或和为0呢?【线性基模板即可】

(2)吃苹果(DIV2 E) 【题解

根据树上阶梯博弈,但是这一次的奇偶性是对叶子节点来说的。

①如果所有叶子节点的深度为奇数,那么使奇数深度的节点的异或和为0即可。

②如果所有叶子节点的深度为偶数,那么使偶数深度的节点的异或和为0即可。

 

九十四:勤劳的蜜蜂【关于区域赛出了一道老题这件事】

开一个x,y,z在二维平面的坐标系,然后计算。

我觉得难点主要是建立这个X,Y,Z坐标系之后怎么计算,
不同于曼哈顿,切比雪夫距离,这个坐标系有六种走法,
所以我们要在曼哈顿距离的基础上,再加多一种。
那么怎么才能较快地计算结果呢?一种比较好地方法如下:
(1)计算出两点地X,Y,Z,坐标
(2)分别计算abs(dx),abs(dy),abs(dz)
(3)然后取三个中两个最小的数加起来就是答案。
最后因为包括起点格子,加一即可。
const ll maxn = 2e5+11,inf = 0x3f3f3f3f,mod = 998244353;
ll k;

int calc(ll N,vector<ll>&sp,int&Lay){
    sp.assign(7,0);

    ll L = 1,R = inf,mid;
    while(L < R){
        mid = (L+R)>>1;
        ll tmp = (3LL*(mid-1)*mid);
        //de(tmp);
        if(tmp <= N-2)L=mid+1;
        else R=mid;
    }

    mid = (L+R)>>1;
    mid--;
    Lay = mid;

    //mid就是该层数
    sp[0] = 3LL*mid*(mid-1)+2; //起点
    sp[1] = sp[0] + mid-1; // 左下角点
    for(int i = 2;i <= 6;i++)
        sp[i] = sp[i-1] + mid;

    for(int i = 1;i <= 6;i++){
        if(sp[i] >= N)return i;
    }
    return -1;
}

array<ll,3> get(ll N){
    if(N == 1)return { 0 , 0 };
    vector<ll>sp;
    int Lay = 0;
    int id = calc(N,sp,Lay);
    assert(id != -1);

    switch(id){
    case 1:return { Lay , sp[1]-N , -Lay+(sp[1]-N) };
    case 2:return { sp[2]-N , -Lay+(sp[2]-N) , -Lay };
    case 3:return { -Lay+(sp[3]-N) , -Lay , -(sp[3]-N) };
    case 4:return { -Lay , -(sp[4]-N) , Lay-(sp[4]-N) };
    case 5:return { -(sp[5]-N) , Lay-(sp[5]-N) , Lay };
    case 6:return { Lay-(sp[6]-N) , Lay , sp[6]-N };
    }
    throw invalid_argument(" fuck! ");
}

inline void solve()
{
    read(k);
    while(k--){
        ll L,R;
        read(L,R);
        ll ans = 0;
        auto A = get(L);
        auto B = get(R);
        vector<ll>vec{
            abs(A[0]-B[0]),
            abs(A[2]-B[2]),
            abs(A[1]-B[1])
        };
        sort(vec.begin(),vec.end());
        ans = vec[0] + vec[1];
        printf("%lld\n",ans+1);
    }
}
View Code

 

 

九十五:Alice and Bob【思维】

此题需要一点思维来转换公式。

很容易想到维护一个 f [ i ] ,表示从i开始,到f [ i ] 有k个数,双指针可以O(n)维护。

然后对于一个区间L,R,其对答案有贡献的部分,设为(L,P)

那么这个P的位置  :  R[p+1] > R ,这个P下标就二分出来即可

然后答案数就是  : for(L->P) ans += (R - f [ i ] +1);  

然后计算一下前缀和,化简公式,就可以O(1)计算了。【什么数据结构都不需要,只需要二分+前缀和】

const ll maxn = 2e5+11,inf = 0x3f3f3f3f3f,mod = 998244353;
ll n,m,k,a[maxn],R[maxn],s[maxn];

inline void solve()
{
    read(n,m,k);
    for(int i = 1;i <= n;i++){
        read(a[i]);
    }
    a[n+1] = inf;R[n+1]=n+1;
    map<int,int>mp;
    for(int i = 1,j = 0;i <= n;i++){
        while(mp.size() < k && j <= n)
            mp[a[++j]]++;
        if(mp.size() >= k){
            R[i] = j;
        }
        else R[i] = n+1;
        mp[a[i]]--;
        if(mp[a[i]] == 0){
            mp.erase(a[i]);
        }
    }

    for(int i = 1;i <= n;i++){
        s[i] = s[i-1] + R[i];
    }

    ll ans=0;
    while(m--){
        ll x,y,tx,ty;
        read(tx,ty);
        x = min(tx^ans,ty^ans)+1;
        y = max(tx^ans,ty^ans)+1;
        ans = 0;

        //de(x<<" "<<y);

        int pos = lower_bound(R+1,R+1+n,y+1) - R;
        if(pos > x){
            pos--;
            ans = (pos-x+1)*(y+1) - s[pos] + s[x-1];
        }

        printf("%lld\n",ans);
    }
}
View Code

 

九十六:Floating point numbers

题意:定义  M*2E 为 0.111111..*21111... ,也就是要使得小数点后M+1位全是1。指数上E位全是1,然后这个数的大小是 A*10B

题目给出A*10B   求M,E的大小。  【该题难在理解题目】

该题只需要一个公式即可: 

看到0.11111...有连续多个1,可以想到 1 - 0.5^m,m指的是有多少个连续的1

A*10^B = (1 - pow(1/2,M+1) )*pow(2, pow(2,E)-1 );

这样计算数字太大,超出了long long的范围 ,所以两边取对数,加个精度判断

fabs(log10(pow(2,M+1)-1) - log10(pow(2,M+1)) + (pow(2,E)-1)*log10(2) - log10(A) - B) <= eps;
// const long long double eps = 1e-6;

总结:① 二进制下小数点后连续m位数都是1 ,那么这个数就是 1- pow(2,-m);

 

九十七:Full Depth Morning Show【换根法,树形DP】

 

九十八:雕塑【三维离散化+种子填充寻找连通块+逆向思维】

再刘汝佳的紫书里面也讲的很清楚,就是三维离散化之后再dfs、bfs即可,但是还是有一些细节优化的,如下:

  1. 三维离散化尽量使用3个数组,分别存储x,y,z轴,这样可以减少小的格子,减少复杂度【我不会告诉你我一开始使用的是1个离散化数组,结果T了n发】
  2. (1) 一开始初始化三维网格时,一种很朴素的做法是枚举 i , j , k 看看它在不在某个立方体内部,这样枚举的复杂度是mx*my*mz*n的,mx是x离散化数组的长度,my,mz也是。                                             (2) 另一种更good的做法是,直接枚举1~n个方格,取左下角的点,然后二分出i , j , k ,然后再有目的性的染色,这样有许多格子就不会被枚举到,所以很快.
const ll maxn = 301,mod = 998244353;

const int dx[]={-1,1,0,0,0,0},dy[]={0,0,1,-1,0,0},dz[]={0,0,0,0,-1,1};
typedef vector<vector<vector<int>>> vec_3D;
typedef vector<vector<int>> vec_2D;

int n,mx,my,mz;
vec_3D G;
array<int,6>ar[maxn];
vector<int>has[3];
ll S,V;

inline void calc(int x,int y,int z,int tp){
    switch(tp){
    case 0: S+=1LL*(has[1][y+1]-has[1][y])*(has[2][z+1]-has[2][z]);break;
    case 1: S+=1LL*(has[0][x+1]-has[0][x])*(has[2][z+1]-has[2][z]);break;
    case 2: S+=1LL*(has[0][x+1]-has[0][x])*(has[1][y+1]-has[1][y]);break;
    }
}

inline void dfs(int x,int y,int z){
    G[x][y][z] = -1;
    V += 1LL*(has[0][x+1]-has[0][x])*(has[1][y+1]-has[1][y])*
            (has[2][z+1]-has[2][z]);
    for(int i = 0;i < 6;i++){
        int tx = x + dx[i];
        int ty = y + dy[i];
        int tz = z + dz[i];
        if(tx>=0&&ty>=0&&tz>=0&&tx<mx-1&&ty<my-1&&tz<mz-1&&G[tx][ty][tz]>=0){
            if(G[tx][ty][tz])calc(x,y,z,i/2);
            else dfs(tx,ty,tz);
        }
    }
}

inline void solve()
{
    S = V = 0; // 初始化
    G.assign(maxn,vec_2D(maxn,vector<int>(maxn,0)));
    read(n);
    for(int i = 0;i < 3;i++)
    has[i].clear(),has[i].emp(0);
    if(n==0){
        puts("0 0");
        return ;
    }
    for(int i = 1;i <= n;i++){
        for(int j = 0;j < 6;j++)read(ar[i][j]);
        for(int j = 0;j < 3;j++){
            has[j].emp(ar[i][j]);
            has[j].emp(ar[i][j]+ar[i][j+3]);
            has[j].emp(ar[i][j]+ar[i][j+3]+1);
        }
    }
//三个离散化数组更快
    for(int i = 0;i < 3;i++)
    sort(has[i].begin(),has[i].end()),
    has[i].erase( unique(has[i].begin(),has[i].end()) , has[i].end());
    mx = has[0].size();
    my = has[1].size();
    mz = has[2].size();

    //预处理单元格的性质1/0【初始化的优化】
    for(int i = 1;i <= n;i++){
        int tx = lower_bound(has[0].begin(),has[0].end(),ar[i][0]) - has[0].begin();
        int ty = lower_bound(has[1].begin(),has[1].end(),ar[i][1]) - has[1].begin();
        int tz = lower_bound(has[2].begin(),has[2].end(),ar[i][2]) - has[2].begin();
        for(int x = tx;x<mx-1&&has[0][x]<ar[i][0]+ar[i][3];x++){
            for(int y = ty;y<my-1&&has[1][y]<ar[i][1]+ar[i][4];y++){
                for(int z = tz;z<mz-1&&has[2][z]<ar[i][2]+ar[i][5];z++){
                    G[x][y][z] = 1;
                }
            }
        }
    }

    dfs(0,0,0);

    //计算
    V = 1LL*has[0][mx-1]*has[1][my-1]*has[2][mz-1] - V;
    printf("%lld %lld\n",S,V);
}
View Code

。。以后多维离散化一定要开多几个数组啊(哭。

 

九十九: Best Solution unknown【思维+分治+线段树维护】

一道分治的题目,首先要看出最大值这个突破口。

 

一百:Brilliant Sequence of Umbrellas【思维题】

 

posted @ 2021-01-21 15:05  PigeonG  阅读(261)  评论(0)    收藏  举报