SDKD 2019 Spring Training Series C2 10th Round 题解

POJ 2689 Prime Distance

TAG:
筛法、思维 = 大素数区间筛
难度:
★★★
题意:
给定两个整数L,R( 1 < = L < = R < = 2 31 , R − L < = 1 0 6 1<=L<=R<=2^{31}, R-L <= 10^{6} 1<=L<=R<=231,RL<=106),求闭区间 [ L , R ] [L, R] [L,R]中相邻两个质数的差最大是多少,输出这两个质数。

思路:
《算法竞赛进阶指南》李煜东著 第131页

L,R的范围很大,任何已知算法都无法在规定时间内生成 [ 1 , R ] [1, R] [1,R]间所有质数。但R-L的值很小,并且任何一个合数n必定包含一个不超过 n \sqrt n n 个质因子。

所以,我们只需要用筛法求出 [ 2 , R ] [2, \sqrt R] [2,R ]之间的所有质数。对于每个质数p,把 [ L , R ] [L, R] [L,R]中能被p整除的数标记,即标记 i ∗ p ( ⌈ L p ⌉ < = i < = ⌊ R p ⌋ ) i*p(\lceil \frac {L}{p}\rceil <=i <=\lfloor \frac {R}{p} \rfloor) ip(pL<=i<=pR)为合数。

最终所有未被标记的数就是 [ L , R ] [L, R] [L,R]中的质数。对相邻两质数两两比较,找出差值最大的即可。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 65536 + 10;
const int maxn = 1000000 + 10;

bool is_prime[MAX];
vector<int> prime;

void init_prime()
{
    memset(is_prime, true, sizeof(is_prime));
    is_prime[0] = false;
    is_prime[1] = false;
    for(int i = 2; i < MAX; i++)
    {
        if(is_prime[i])
        {
            prime.push_back(i);
            for(int j = i*2; j < MAX; j+=i)
            {
                is_prime[j] = false;
            }
        }
    }
}

bool f[maxn];

vector<long long> v;

int main()
{
    ios::sync_with_stdio(false);
    init_prime();
    long long l, r;
    while(cin >> l >> r)
    {
        memset(f, true, sizeof(f));
        if(l == 1)
        {
            f[0] = false;
        }
        long long sqrtr = sqrt((long double)r);
        for(int i = 0; prime[i] <= sqrtr; i++)
        {
            long long pr = prime[i];
            for(long long j = ceil((double)l / pr); pr * j <= r; j++)
            {
                if(j != 1)
                {
                    f[j*pr-l] = false;
                }
            }
        }
        long long n = r - l;
        v.clear();
        long long minn = 1e8;
        long long maxx = 0;
        pair<long long, long long> minans;
        pair<long long, long long> maxans;
        for(int i = 0; i <= n; i++)
        {
            if(f[i])
            {
                if(!v.empty())
                {
                    if(l+i - v.back() < minn)
                    {
                        minn = l+i - v.back();
                        minans = make_pair(v.back(), l+i);
                    }
                    if(l+i - v.back() > maxx)
                    {
                        maxx = l+i - v.back();
                        maxans = make_pair(v.back(), l+i);
                    }
                }
                v.push_back(l+i);
            }
        }
        if(v.size() >= 2)
        {
            cout << minans.first << "," << minans.second << " are closest, " << maxans.first << "," << maxans.second << " are most distant." << endl;
        }
        else
        {
            cout << "There are no adjacent primes." << endl;
        }
    }
    return 0;
}

HDU 1556 Color the ball

TAG:
差分前缀和 / 树状数组区间修改单点查询 / 线段树
难度:
★★
题意:
有编号 [ 1 , n ] [1, n] [1,n]的n ( n < = 1 0 6 ) (n <= 10^{6}) (n<=106)个位置,初始权值都为零,之后做n次操作,每次把 [ l , , r ] [l, ,r] [l,,r]区间的权值分别+1,最后输出每个位置的权值。
思路:

  1. 差分+前缀和:
    因为所有修改都做完之后才查询,可以差分处理。构建差分数组C,对 [ l , r ] [l, r] [l,r]区间+1也就是C[l]++; C[r+1]–;然后i位置的值就是 s u m ( [ 1 , i ] ) sum([1, i]) sum([1,i])
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 100000 + 10;

int C[maxn];
int n;

int main()
{
   while(scanf("%d", &n) != EOF && n)
   {
       memset(C, 0, sizeof(C));
       for(int i = 0; i < n; i++)
       {
           int l, r;
           scanf("%d%d", &l, &r);
           C[l]++;
           C[r+1]--;
       }
       for(int i = 1; i <= n; i++)
       {
           C[i] += C[i-1];
           if(i != 1)
           {
               printf(" ");
           }
           printf("%d", C[i]);
       }
       cout << endl;
   }
   return 0;
}
  1. 树状数组区间修改单点查询:
    其实也是差分思想,只不过树状数组天生log级求前缀和,可以做到在线修改查询。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 100000 + 10;

int C[maxn];
int n;

int lowbit(int x)
{
    return x & (-x);
}

void update(int p, int x)
{
    while(p <= n)
    {
        C[p] += x;
        p += lowbit(p);
    }
}
void update(int l, int r, int x)
{
    update(r + 1, -x);
    update(l, x);
}

int query(int p)
{
    int sum = 0;
    while(p > 0)
    {
        sum += C[p];
        p -= lowbit(p);
    }
    return sum;
}

int main()
{
    while(scanf("%d", &n) != EOF && n)
    {
        memset(C, 0, sizeof(C));
        for(int i = 0; i < n; i++)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            update(l, r, 1);
        }
        for(int i = 1; i <= n; i++)
        {
            if(i != 1)
            {
                printf(" ");
            }
            printf("%d", query(i));
        }
        cout << endl;
    }
    return 0;
}
  1. 线段树:
    线段树自然也可以区间修改单点查询,不过这题有点大材小用了。

HDU 1217 Arbitrage

TAG:
Floyd算法变形
难度:
★★★
题意:
给出各种货币之间的汇率(注意是单向边),问是否存在种货币经过多次兑换后再兑换成原货币比之前多的情况。
思路:
算法不难,难在想到用Floyd的原理。
跑Floyd,只不过更新条件变成了乘法,最后判断是否有d[i][i] > 1的情况,也就是自己跟自己兑换还变多了。

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 30 + 10;

map<string, int> id;

int n, m;

double d[maxn][maxn];

bool check()
{
    for(int i = 0; i < n; i++)
    {
        if(d[i][i] > 1)
        {
            return true;
        }
    }
    return false;
}

int main()
{
    int cases = 1;
    while(cin >> n && n != 0)
    {
        id.clear();
        memset(d, 0, sizeof(d));
        for(int i = 0; i < n; i++)
        {
            string s;
            cin >> s;
            id[s] = i;
        }
        cin >> m;
        while(m--)
        {
            string a, b;
            double v;
            cin >> a >> v >> b;
            d[id[a]][id[b]] = v;
        }
        for(int k = 0; k < n; k++)
        {
            for(int i = 0; i < n; i++)
            {
                for(int j = 0; j < n; j++)
                {
                    if(d[i][k] * d[k][j] > d[i][j])
                    {
                        d[i][j] = d[i][k] * d[k][j];
                    }
                }
            }
        }
        printf("Case %d: %s\n", cases++, check() ? "Yes" : "No");
    }
    return 0;
}

HDU 4699 Editor

TAG:
数据结构、对顶栈
难度:
★★★★
题意:
维护一个整数序列的编辑器,有以下五种操作,操作总数不超过 1 0 6 10^{6} 106

  • I x: 在当前光标位置之后插入一个整数x ( − 1 0 3 < = x < = 1 0 3 ) (-10^{3} <= x <= 10^{3}) (103<=x<=103),插入以后光标移动到x之后;
  • D: 删除光标之前的一个整数,即按下退格键Backspace;
  • L: 光标向左移动一个位置,即按下 ← \leftarrow 键;
  • R: 光标向右移动一个位置,即按下 → \rightarrow 键;
  • Q k: 询问在位置k之前的最大前缀和,其中k不超过当前光标的位置。

如果移动或者删除的操作出边界跳过即可

思路:
对顶栈这个数据结构很有意思,如果没接触过的话很难想到。

本题所有的修改操作都是在序列中间的某个指定位置进行修改的,具体来说I、D、L、R四种操作都在光标位置处发生,并且操作完成后光标至多移动一个位置。

对此我们可以建立两个栈,st1存储光标左边的序列,光标近紧邻的前一个数据在栈顶;st2存储光标右边的序列,光标近紧邻的后一个数据在栈顶。这两个栈合起来就是整个序列。因为查询操作的k不超过光标位置,所以我们找个数组维护st1栈前缀和的最大值就行了。

具体操作见代码注释

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 1000000 + 10;
const long long INF = 1e15 + 10;

stack<long long> st1, st2;

// sum前缀和数组,maxx储存前缀和的最大值
long long sum[maxn], maxx[maxn];

int main()
{
    ios::sync_with_stdio(false);
    int Q;
    while(cin >> Q)
    {
        while(!st1.empty()) st1.pop();
        while(!st2.empty()) st2.pop();
        sum[0] = sum[1] = 0;
        maxx[0] = maxx[1] = -INF;
        // k是光标位置
        int k = 1;
        while(Q--)
        {
            char op;
            cin >> op;
            // 插入操作,把插入元素放到第一个栈中,再更新sum和maxx
            if(op == 'I')
            {
                long long x;
                cin >> x;
                st1.push(x);
                sum[k] = sum[k-1] + x;
                maxx[k] = max(maxx[k-1], sum[k]);
                k++;
            }
            // 删除操作,删除第一个栈栈顶元素
            else if(op == 'D')
            {
                if(st1.empty()) continue;
                st1.pop();
                k--;
            }
            // 左移光标,把第一个栈的栈顶元素移动到第二个栈的栈顶
            else if(op == 'L')
            {
                if(st1.empty()) continue;
                st2.push(st1.top());
                st1.pop();
                k--;
            }
            // 右移光标,把第二个栈的栈顶元素移动到第一个栈的栈顶
            else if(op == 'R')
            {
                if(st2.empty()) continue;
                sum[k] = sum[k-1] + st2.top();
                maxx[k] = max(maxx[k-1], sum[k]);
                k++;
                st1.push(st2.top());
                st2.pop();
            }
            // 查询操作,直接查询maxx数组即可
            else // if(op == 'Q')
            {
                int tk;
                cin >> tk;
                cout << maxx[tk] << endl;
            }
        }
    }
    return 0;
}

POJ 3190 Stall Reservations

TAG:
贪心
难度:
★★
题意:
有n ( n < = 5 ∗ 1 0 4 ) (n<=5*10^{4}) (n<=5104)头牛在蓄栏中吃草。每个蓄栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个蓄栏。给定n头牛和每头牛开始和结束吃草时间,在这段时间牛会一直吃草,求需要的最小蓄栏数目和每头牛对应的蓄栏方案。如有多种方案,输出一种可行方案即可。

思路:
对牛按吃草开始时间递增排序,每次找一个空闲时刻和开始时刻离得最近的蓄栏把牛安排进去;如果没有开始时刻没有空闲的蓄栏,就增加一个蓄栏。这样就能保证蓄栏尽量不被浪费。

至于怎么高效的找小于开始时刻又最接近的蓄栏,我们可以开一个multiset,里面维护蓄栏的结构体,把时间取相反数,也就是负数,然后upper_bound在里面二分即可。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

struct Cow
{
    int l, r, id;
    int ans;
    Cow(int id, int l, int r): id(id), l(l), r(r) {}
    bool operator < (const Cow &t) const
    {
        return l == t.l ? r < t.r : l < t.l;
    }
};

struct Stall
{
    int id, time;
    Stall(int id, int time): id(id), time(time) {}
    bool operator < (const Stall &t) const
    {
        return time < t.time;
    }
};

bool cmp(Cow t1, Cow t2)
{
    return t1.id < t2.id;
}

multiset<Stall> st;
vector<Cow> v;

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        v.push_back(Cow(i, l, r));
    }
    sort(v.begin(), v.end());
    for(int i = 0; i < v.size(); i++)
    {
        multiset<Stall>::iterator it = st.upper_bound(Stall(0, -v[i].l));
        if(it == st.end())
        {
            v[i].ans = st.size()+1;
            st.insert(Stall(st.size()+1, -v[i].r));
        }
        else
        {
            Stall t = *it;
            t.time = -v[i].r;
            v[i].ans = t.id;
            st.erase(it);
            st.insert(t);
        }
    }
    cout << st.size() << endl;
    sort(v.begin(), v.end(), cmp);
    for(int i = 0; i < n; i++)
    {
        cout << v[i].ans << endl;
    }
    return 0;
}

CodeForces 934B A Prosperous Lot

TAG:
构造、签到题
难度:

题意:
输入一个数k,要求输出一个不超过 1 0 18 10^{18} 1018的数,让这个数所有“环”的数量等于k,如果没有这样的数输出-1。

环是指十进制阿拉伯数字封闭区域的数量,比如0、4、6有一个环,8有两个环。

思路:
一个个位数最多有两个环,那k大于36的时候肯定构造不出来小于等于 1 0 18 10^{18} 1018的数,输出-1;
其余情况尽量输出8,还剩一个的环的时候输出个4或者6就行了。注意避开用0,因为这样处理不好可能会出前导零。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    if(n > 36)
    {
        printf("-1");
    }
    else
    {
        while(n > 1)
        {
            printf("8");
            n -= 2;
        }
        if(n == 1)
        {
            printf("6");
        }
    }
    return 0;
}

你可能感兴趣的:(解题笔记)