CF与leetcode刷题笔记7.13-7.17

Permutation Restoration(1900)

题目大意:设 a 1 ⋯ a n a_1 \cdots a_n a1an是1-n的排列,给定
b 1 = ⌊ 1 a 1 ⌋ , b 2 = ⌊ 2 a 2 ⌋ ⋯ b n = ⌊ n a n ⌋ b_1 = \lfloor \frac{1}{a_1} \rfloor,b_2 = \lfloor \frac{2}{a_2 } \rfloor \cdots b_n = \lfloor \frac{n}{a_n} \rfloor b1=a11,b2=a22bn=ann
求任意一个满足条件满足条件 a 1 ⋯ a n a_1 \cdots a_n a1an。其中 n ≤ 5 ∗ 1 0 5 n \leq 5 * 10^5 n5105
思路:每个位置可二分求出需要的 a a a的最小值和最大值,设为区间[min,max],一共n个区间。n个区间每个区间需要从自身表示范围中找个代表,且每个区间代表不同,即区间与代表一一对应。关键是使用1-n每个数字去选择区间,而不是为每个区间选择数字。显然可以遍历1-n,贪心选择包含当前数值最早结束的区间,使用堆维护。

#include <bits/stdc++.h>
using namespace std;


int main()
{
    int t;
    cin >> t;
    for(int i = 0;i<t;i++)
    {   
        int n; cin >> n;
        int b[n];
        for(int j = 0;j<n;j++) scanf("%d",&b[j]);
        vector<pair<int,int>> c[n+1];
        for(int j = 1;j<=n;j++)
        {
            int l = 1,r = n+ 1;        
            while(l < r)  //找最后一个
            {
                int m = (l + r) >> 1;
                if(b[j-1]<=j/m) l = m + 1;
                else r = m;
            }
            int a = --r;
            l = 1,r = n + 1;
            while(l < r)
            {
                int m = (l + r) >> 1;
                if(b[j-1]<j/m) l = m + 1;else r = m;
            }
            c[r].push_back({a,j});
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;
        vector<int> ans;
        for(int j = 1;j<=n;j++)
        {
            while(!q.empty() && q.top().first < j) q.pop();
            for(auto& d:c[j]) q.push(d);
            b[q.top().second - 1] = j; 
            q.pop();
        }
        for(int j = 0;j<n;j++) {printf("%d",b[j]); if(j < n  - 1) printf(" ");}
        printf("\n");
    }
    system("pause");
    return 0;
}

Fixed Point Guessing(1600-交互题)

题目大意: 给定 [ 1 , 2 , 3 ⋯ n ] [1,2,3\cdots n] [1,2,3n],其中n为奇数。固定其中一个数字,对其余n-1个数字配成 n − 1 2 \frac{n-1}{2} 2n1对互相交换形成数组a。

每次查询可指定l与r(l=1是起始位置),返回 a [ l : r ] a[l:r] a[l:r]升序排列后的结果。 n < = 10000 n<=10000 n<=10000,最多进行15次查询。求那个固定的数字。
思路:
随着r增大,a[1:r]先不包含固定点,再突变包含固定点。因此使用二分查找。二分r值,如何查询 a [ 1 : r ] a[1:r] a[1:r]中是否包含固定点:注意交换操作。a[1:r]中可能存在[1:r]间的数字对自身交换,也可能存在与外部的交换。

  • 若包含突变点,则自身交换和固定点共同构成范围[1:r]中的数字,个数为奇数个。
  • 否则,则仅自身交换点构成[1:r]中的数字,个数为偶数个。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int main()
{
    int t;
    scanf("%d",&t);
    for(int i = 0;i<t;i++)
    {
        int n; scanf("%d",&n);   //a数组的长度
        int low  = 0,high = n;
        while(low < high)
        {
            int m = (low + high) >> 1;
            int o = 0; 
            printf("? %d %d\n",1,m+1);
            fflush(stdout);
            for(int j = 0;j<=m;j++)
            {
                int a; scanf("%d",&a);
                if(a>=1 && a<=m+1) o++;
            }
            if(o % 2 == 0) low = m + 1; else high = m;
        }
        printf("! %d\n",low+1);
        fflush(stdout);
    }
    system("pause");
    return 0;
}

Placing Jinas(2000)

题目大意:
给定长宽无限二维网格,每一行从头开始是连续数量的白色格点,且连续数量随行增加而单调不增,每行其余格点为黑色。
一次操作定义为将一个玩偶(假设位于结点(i,j))移走,并在(i+1,j),(i,j+1)位置分别放置一个新玩偶。初始阶段仅(0,0)处有玩偶,求当所有白色格点均不包含玩偶时的最小操作次数。
数据范围:格点维度 n ≤ 2 ⋅ 10 ∗ 5 n \leq 2 \cdot 10*5 n2105,每行连续白色格点数 a i ≤ 2 ∗ 1 0 5 a_i \leq 2*10^5 ai2105
不太正确的dp:
d p [ i ] [ j ] dp[i][j] dp[i][j]从(i,j)将该点上的一个玩偶完全移走所需最小操作数。
d p [ i ] [ j ] = d p [ i ] [ j + 1 ] + d p [ i + 1 ] [ j ] + 1 dp[i][j] = dp[i][j+1] + dp[i+1][j] + 1 dp[i][j]=dp[i][j+1]+dp[i+1][j]+1
数据范围限制不可以分别求dp。而边界的(i,j)都是不固定的,因此不好使用一个公式得出dp[0][0]。
正解:(如何划分解空间,分而治之)
考察每个操作如何得到,即找一一对应
注意需要总操作数实际上就是过程中途径所有格点的点的数量。而途径(i,j)格点点数目就是途径(i-1,j)格点点数和途径(i,j-1)格点点数。两者不存在交集,因为只能由(i-1,j-1)复制得到。
所以设 d p [ i ] [ j ] dp[i][j] dp[i][j],操作序列途径(i,j)格点的点数。即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1].且 d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1。画出递归树,i+j步到达递归终点,所有可能的路径中,只有恰好出现i次向下(即j次向右)才是递归树从根到叶子dp[0][0]的有效路径。因此 d p [ i ] [ j ] = C i + j i dp[i][j] = C_{i+j}^i dp[i][j]=Ci+ji
所以最终答案为 ∑ i = 0 n ∑ j = 0 a i − 1 C i + j i \sum_{i = 0}^n \sum_{j = 0}^{a_i - 1} C_{i+j}^i i=0nj=0ai1Ci+ji
∑ j = 0 a i − 1 C i + j i = C i + a i i + 1 \sum_{j = 0}^{a_i - 1} C_{i+j}^i = C_{i+a_i}^{i+1} j=0ai1Ci+ji=Ci+aii+1
上面是一个重要的组合数公式
在这里插入图片描述
这是因为 C i + a i i + 1 = C i + a i − 1 i + 1 + C i + a i − 1 i C_{i+a_i}^{i+1} = C_{i+a_i - 1}^{i+1} + C_{i+a_i - 1}^{i} Ci+aii+1=Ci+ai1i+1+Ci+ai1i再展开。

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
class Comb {
    vector<int> Facs, Invs;
    void expand(size_t n) {
        while(Facs.size() < n + 1) Facs.push_back(1ll * Facs.back() * Facs.size() % MOD);
        if(Invs.size() < n + 1) { // 线性求阶乘的逆元
            Invs.resize(n + 1, 0);
            Invs.back() = 1;
            for(int a = Facs[n], p = MOD - 2; p; p >>= 1, a = 1ll * a * a % MOD)
                if(p & 1) Invs.back() = 1ll * Invs.back() * a % MOD; // 快速乘求 n! 的逆元
            for(int j = n-1; !Invs[j]; --j) Invs[j] = 1ll * Invs[j+1] * (j + 1) % MOD;
        }
    }
public:
    Comb() : Facs({1}), Invs({1}) {}
    Comb(int n) : Facs({1}), Invs({1}) { expand(n); }
    int operator() (int n, int k) {
        if(k > n) return 0;
        expand(n);
        return (1ll * Facs[n] * Invs[n-k] % MOD) * Invs[k] % MOD; 
    }
};

Comb comb;
typedef long long ll;



int main()
{
    int n;
    scanf("%d",&n);
    ll a[n+1];
    for(int i= 0;i<n+1;i++) scanf("%d",&a[i]);
    ll ans = 0;
    for(int i = 0;i<=n;i++)
    {
        ans  = (ans + comb(i + a[i],i+1)) % MOD;
    }
    cout << ans << endl;
    system("pause");
    return 0;
}

Split Into Two Sets(1600)

题目大意:
给定若干 [ a i , b i ] ,其中 1 ≤ i ≤ n [a_i,b_i],其中1 \leq i \leq n [ai,bi],其中1in, a i , b i a_i,b_i ai,bi都是[1-n]间正整数。是否存在将所有 [ a i , b i ] [a_i,b_i] [ai,bi]划分成两个集合的方法,使得每个集合中所有数字都不相同。
其中n为偶数,且 n ≤ 1 0 5 n \leq 10^5 n105
思路
每个数字视作结点,一个 [ a i , b i ] [a_i,b_i] [ai,bi]为一条边。首先必须满足每个顶点度为2,因此图就由不相交的环构成
错误做法
任意找一条边,count++,删除这条边并删除关联两个顶点的所有边。直到删完所有边。检查count是否等于n/2
错误原因:偶数长度环也可能只选择了2条边。
正确做法
判断所有环长是否都是偶数

#include <bits/stdc++.h>
using namespace std;
vector<bool> vis;



int main()
{
    int t;
    scanf("%d",&t);
    for(int s = 0;s<t;s++)
    {
        int n;scanf("%d",&n);
        int a,b;
        vector<int> adj[n+1];
        bool f = true;
        for(int i = 0;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            adj[a].push_back({b});
            adj[b].push_back({a});  //边的序号
            if(a == b) {f = false;}
        }
        if(f) {for(int i = 1;i<=n;i++) if(adj[i].size() != 2) {f = false;break;}}
        if(!f) cout << "NO" << endl;
        else
        {
            vis.resize(n+1,false);
            for(int i = 1;i<=n;i++) vis[i]=false;
            bool f = true;
            //cout << vis.size() << endl;
            for(int i = 1;i<=n;i++)
            {
                if(!vis[i])
                {
                    int cur = i;
                    int l  = 1;
                    vis[cur] = true;
                    while(true)
                    {
                        int next = vis[adj[cur][0]]?adj[cur][1]:adj[cur][0];
                        if(!vis[next]) {vis[next]=true;l++;cur=next;}
                        else break;
                    }
                    if(l % 2!=0) f = false;
                }
                if(!f) break;
            }
            if(f) printf("YES\n"); else printf("NO\n");
            // int ju = 0;   //错误做法
            // set<int> al;
            // for(int i = 1;i<=n;i++)
            // {
            //     while(!adj[i].empty() && al.find(adj[i][adj[i].size() - 1].second) != al.end())
            //     adj[i].pop_back();
            //     if(adj[i].empty()) continue;
            //     pair<int,int> q = adj[i][adj[i].size() - 1]; ju++;   
            //     al.insert(q.second);
            //     for(auto y:adj[i]) al.insert(y.second);
            //     for(auto z:adj[q.first]) al.insert(z.second);
            //     adj[i].clear();
            //     adj[q.first].clear();
            // }
            // assert(al.size() == n);
            // if(ju == n/2) cout << "YES" << endl;
            // else cout << "NO" << endl;
        }   
    }
    system("pause");
    return 0;
}

Permutation Graph(GOOD 1900)

题目大意:
给定整数1-n的排列 a 1 , a 2 ⋯ a n a_1,a_2 \cdots a_n a1,a2an。有一张无权无向图结点编号为1-n,结点i和结点j间有一条边当且仅当下两条件满足之一:

  • m i n ( a i , a i + 1 ⋯ a j ) = a i min(a_i,a_{i+1}\cdots a_j) = a_i min(ai,ai+1aj)=ai m a x ( a i , a i + 1 ⋯ a j ) = a j max(a_i,a_{i+1}\cdots a_j) = a_j max(ai,ai+1aj)=aj
  • m a x ( a i , a i + 1 ⋯ a j ) = a i max(a_i,a_{i+1}\cdots a_j) = a_i max(ai,ai+1aj)=ai m i n ( a i , a i + 1 ⋯ a j ) = a j min(a_i,a_{i+1}\cdots a_j) = a_j min(ai,ai+1aj)=aj

求结点1到结点n的最短路径长。
巧妙思路
找特殊值(这里就是最值),找划分点.利用最短路径的最优子结构。
首先,编号相邻结点之间一定有一条有向边
其次,最短路径序列上结点编号一定是严格递增的,即不会回退(根据定义容易验证)。
最后,从1到达n的路径必经过 m a x   a i max \ a_i max ai的结点i。因此原问题被分为两个子问题:从1-i的最短路径 + 从i-n的最短路径
为求1-i的最短路径,必经过 a k a_k ak最小的结点k,其中 k ≥ 1 且 k ≤ i k \geq 1 且 k \leq i k1ki。由此 a k a_k ak a i a_i ai间有一条边。故为1-k的最短路径长+1.求1-k的最短路径找最大值,如此交替。
对i-n的最短路径同理。时间复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
using namespace std;
vector<bool> vis;
//flag用于区分是前缀还是后缀
int dfs(bool flag,int i,int j,int n,int sufminind[],int sufmaxind[],int preminind[],int premaxind[],int counter)
{
    if(j<=i) return 0;
    if(flag)
    {
        int m;
        if(counter % 2 == 0) 
        {
            m = premaxind[j];
        }
        else m = preminind[j];
        return dfs(flag,0,m,n,sufminind,sufmaxind,preminind,premaxind,counter + 1) + 1;
    }
    else
    {
        int m;
        if(counter % 2 == 0)
        {
            m = sufmaxind[i];
        }
        else m = sufminind[i];
        return dfs(flag,m,n-1,n,sufminind,sufmaxind,preminind,premaxind,counter+1) + 1;
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int i = 0;i<t;i++)
    {
        int n;
        scanf("%d",&n);
        int a[n];
        for(int k = 0;k<n;k++) scanf("%d",&a[k]);
        if(n == 2) {cout << 1 << endl; continue;}
        int sufminind[n],sufmaxind[n],preminind[n],premaxind[n];
        sufminind[n-1]=sufmaxind[n-1]=n-1;
        preminind[0]=premaxind[0]=0;
        for(int i = 1;i<n;i++) 
        {
            if(a[i]<a[preminind[i-1]]) preminind[i]=i; else preminind[i]=preminind[i-1];
            if(a[i]>a[premaxind[i-1]]) premaxind[i]=i; else premaxind[i]=premaxind[i-1];
        }
        for(int i = n - 2;i>=0;i--)
        {
            if(a[i]<a[sufminind[i+1]]) sufminind[i]=i; else sufminind[i]=sufminind[i+1];
            if(a[i]>a[sufmaxind[i+1]]) sufmaxind[i]=i; else sufmaxind[i]=sufmaxind[i+1];
        }
        int counter = 1;
        int m = premaxind[n-1];
        int y = dfs(true,0,m,n,sufminind,sufmaxind,preminind,premaxind,counter); 
        int u = dfs(false,m,n-1,n,sufminind,sufmaxind,preminind,premaxind,counter);
        cout << y + u << endl;
    }
    system("pause");
    return 0;
}

常规思路:
贪心算法

贪心选择:每次跳到右侧能够跳到最远的点。如果所有最优解第一次都不是跳到最远的点(设为点A)则任取一最优解并考察最后一次跳到A右侧的步骤实际上可以直接使用从开始点到该点的一步替代。
优化子结构:
不会回退,显然成立。

需合适的数据结构计算每个点作为区间左端点并作为最小值(最大值),并且右端点作为最大值(最小值),满足条件的区间最大长度。

并不能从头开始枚举。因为将左端点作为最小值求时可能造成 O ( n ) O(n) O(n)遍历数组,而仅当作为最大值时才有满足条件的区间,即一次只往后更新1.因此可造成 O ( n 2 ) O(n^2) O(n2)的复杂度

Equate Multisets(1700 GOOD)

题目大意:给定两个允许含多个相同元素的集合a,b。每次可选取集合b中一个元素将其除以2(下取整),或乘以2。问是否可以将集合b转换为集合a.
思路
b转换为a中一个元素操作序列为:0个或多个除以2的操作紧随0个或多个乘以2的操作。则b中特定元素 b i b_i bi转化为a的特定元素 a i a_i ai的充要条件是 b i b_i bi可以仅通过除以2的操作转化为( a i a_i ai不断除以2直到结果为奇数的那个数)。
于是将集合a中每个元素先不断除以2直到结果为奇数。再试图用b取匹配这个新集合。考察两集合的最大值:

  • 若相同,一定是两最大值匹配
  • 若a中最大值大于b,则无法匹配(b只能除以2)
  • 若a中最大值小于b,则b中最大值需除以2
#include <bits/stdc++.h>
using namespace std;
vector<bool> vis;


int main()
{
    int t; cin>>t;
    for(int i = 0;i<t;i++)
    {
        int n ; scanf("%d",&n);
        multiset<int> a;
        multiset<int> b;
        for(int k = 0;k<n;k++)
        {
            int c;scanf("%d",&c);while(c%2==0) c/=2; a.insert(c);
        }
        for(int k = 0;k<n;k++) {int c;scanf("%d",&c);b.insert(c);}
        bool f = true;
        while(!b.empty())
        {
            int x = *b.rbegin();
            int y = *a.rbegin();
            if(x == y)
            {
                a.erase(a.find(x));
                b.erase(b.find(x));
            }
            else if(y > x) {f = false;break;}
            else  //x>y
            {
                if(x <= 1) {f = false;break;}
                b.erase(b.find(x));
                b.insert(x / 2);
            }
        }
        if(f) printf("YES\n"); else printf("NO\n");
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值