Codeforces Round #591 Div. 2

1223A CME
题意:给定一个数0n1e90 \le n \le 1e9,把它分成三个数a,b,c使得a + b = c。问至少要增加多少才能分?

题解:
当n==2时,至少需要2。因为最小的等式是1+1=2。
当n>2分两种情况 ,n为偶数时,令c=n/2,a=n/2b=n/2c = n/2, a = \lfloor n / 2 \rfloor,b = \lceil{n / 2} \rceil即可
当n为奇数时,因为n = a+b+c = 2(a+b)是不可能的,所以至少加一,变成偶数即可

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

int main(){
   int q;
   cin>>q;
   while(q--){
        int n,ans = 0;
        cin>>n;
        if(n == 2) ans = 2;
        else ans = n & 1;

        cout<<ans<<endl;
   }
    return 0;
}

1223B Strings Equalization
题意:给定两个长度相同的串s,t,你可以每次对s或者t做一个操作,任取其中两个相邻位置,让其中一个的字符等于另一个。问能不能通过一些操作后,s和t相同

题解:注意t也可以做一些操作,所以只要s和t的整个串有相同的字符,把整个串变成这个字符即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    string s1,s2;
    cin>>n;
    char c1[30] = {0};
    char c2[30] = {0};
    while(n--){
        memset(c1, 0, sizeof(c1));
        memset(c2, 0, sizeof(c2));
        bool ans = false;
        cin>>s1>>s2;
        for(int i = 0;i < s1.length(); ++i)
            c1[s1[i] - 'a']++;

        for(int i = 0;i < s2.length(); ++i)
            c2[s2[i] - 'a']++;

        for(int i = 0;i < 30; ++i)
            if(c1[i] > 0 && c2[i] > 0) ans = true;

        if(ans) puts("YES");
        else puts("NO");

    }
    return 0;
}

1223C Save the Nature
题意:给定n个正整数q1,...,qnq_1,...,q_n,以及x,ax,ay,by,b 还有kk
对于这n个数的一个排列,对每个aa的倍数的位置ii,我们能得到值qixq_i * x,对于每个bb倍数的位置jj,我们能得到值qjyq_j*y。问在这个n个数里面至少取多少个数能使得到的值的和至少为kk

题解:二分+贪心。二分取的数字个数。先把数从大到小排序,把大的数优先排在a和b的共同倍数上,然后剩下里面继续把大的放在a和b中较大的位置。贪心取。

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

const int maxn = 2 * 1e5 + 10;

vector<long long> p(maxn);

long long gcd(int a, int b){
    if(!b) return a;
    return gcd(b, a % b);
}
long long lcm(int a, int b){
    return a / gcd(a,b) * b;
}

int main(){

    int q, n ,x, a, y, b;
    long long k;
    cin>>q;
    while(q--){
        cin>>n;
        for(int i = 1;i <= n; ++i) {cin >> p[i]; p[i] /= 100;}
        cin >> x >> a >> y >> b;
        cin >> k;

        long long a_b_lcm = lcm(a, b);
        long long x_only, y_only, x_y_c, xy = x + y;
        if(x > y){
            swap(x,y);
            swap(a,b);
        }

        int left = 1, right = n;
        int ans = n + 2;

        sort(p.begin() + 1, p.begin() + n + 1);
        reverse(p.begin() + 1, p.begin() + n + 1);
        p[0] = 0;
        for(int i = 1;i <= n; ++i) p[i] += p[i - 1];

        while(left <= right){
            int mid = (right - left) / 2 + left;
            long long cur = 0;
            x_y_c = mid / a_b_lcm;
            x_only = mid / a - x_y_c;
            y_only = mid / b - x_y_c;
            cur = xy * p[x_y_c];
            cur += y * (p[y_only + x_y_c] - p[x_y_c]);
            cur += x * (p[x_only + y_only + x_y_c] - p[y_only + x_y_c]);
            if(cur >= k){
                ans = min(ans, mid);
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        if(ans > n) ans = -1;
        cout<<ans<<endl;
    }
    return 0;
}

1223D Sequence Sorting
题意:给定一个整数数组,你每次可以做一个操作,就是任取其中一个数,让和这个数相同的所有数都挪到左边或者右边去,问至少做多少次操作能使整个数组是非递减的

题解:dp或贪心。
因为每次只能放到最前或者最后。把相同数的看成一个整体。最大那个数一定是最后一个放在末尾的(也可能一个都没放,如果有就是最后一个)。先不管最后一个,假设我们把前面n - 1个先排好了,n有两种情况,一种是不用动,一种是将其放到最末去。
不用动的情况是,其他数只能往最前放。这时候最小的移动数就是n - 从最大开始往左数的最长递减序列长度。
需要动的情况,其他数可以往前也可以往后放。
所以设dp[i]为前i大的数排序的最小次数dp[i] = min(dp[i - 1] + 1,n - len_i)

另外的做法是贪心。假设第i大不用动(显然至少有一个不用动)
则1到i-1大一定是往左放,i + 1到n大一定往右放。所以我们考虑一边,左边。
现在问题变成,给你一个序列,你有一个操作,只能往左放,问最少多少次能使序列递增。这个显然是求从末尾开始的向左的最长递减数。所以如果i不动的最少移动次数就是n减去 i往左的最长递减和往右最长的第增连续序列长度。
所以对于所有位置的答案就是,n - 最长的递增列长度。
(写的乱七八糟,希望能理解)

贪心做法代码

#include<bits/stdc++.h>
using namespace std;
//保存数值和位置
pair<int,int> a[300010];
bool cmp(pair<int,int> &a, pair<int,int> &b){
    if(a.first == b.first) return a.second < b.second;
    return a.first < b.first;
}

int main()
{
    int q,n;
    cin>>q;
    while(q--){
        cin>>n;
        for(int i = 0;i < n; ++i) scanf("%d",&a[i].first), a[i].second = i;
        sort(a, a+n, cmp);

        int ans = 1, tot_num = 0;
        int cont = 0;
        int prev = -1;

        for(int i = 0;i < n; ++i){
            ++tot_num;
            if(prev < a[i].second){
                ++cont;
            }else{
                ans = max(ans, cont);
                cont = 1;
            }

            while(i + 1 < n && a[i + 1].first == a[i].first) ++i;
            prev = a[i].second;
        }
        ans = tot_num - max(ans, cont);
        cout<<ans<<endl;

    }

    return 0;
}

1223E Paint the Tree
题意:给定一棵树n个节点n - 1条边,每条边有一个权值w。给定一个数k。你可以对每个点着k种颜色(颜色无穷种)。每种颜色只能出现至多两次。着色后,每条边能获得一个值,如果两个节点有共同颜色,则值为边权否则为0。将所有边值加起来为树的值。问这个值最大为多少?

题解:树dp。注意到 每个点的颜色,要么和儿子匹配,要么取全新的没有被用过是最优的。每个点和父亲只有一条边,所以我们可以对每个点分两种状态,一种k种颜色都被儿子匹配了,另一种是有颜色未被匹配。

设dp[i][0]表示当前节点k种颜色有剩余时(有未被儿子匹配的颜色),子树的最大值。dp[i][1]表示以i为节点的子树的最大值。dp完后答案就是dp[1][1]。
转态转移:对于每个节点假设它有t个儿子,共有2t个状态值
dp[c1][0] ,…, dp[ct][0]
dp[c1][1],…,dp[ct][1]
dp[i][0]的值就是在第一行中取至多k - 1个位置和对应点的边权,数目设为m,然后第二行取剩余的n - m个位置构成的n个的最大的和。表示的就是当前节点和至多k - 1个儿子节点匹配(有共同颜色),剩下的不匹配。
形式化描述就是,选择一个t个数的二进制b1,...,bt,bj{0,1}bj<kb_1,...,b_t,b_j\in \{0,1\},\sum b_j < k
dp[i][0]=maxb1,...,btj=1tdp[cj][bj]+wcjbjdp[i][0] = \max_{b_1,...,b_t} \sum_{j=1}^t dp[c_j][b_j] + w_{c_j} * b_j

怎么求这个最大值呢? 注意到上式等于
dp[i][0]=j=1tdp[cj][1]+maxb1,...,bt(dp[cj][0]dp[cj][1]+wcj)bjdp[i][0] =\sum_{j=1}^t dp[c_j][1] + \max_{b_1,...,b_t} \left(dp[c_j][0] - dp[c_j][1] + w_{c_j}\right) * b_j

我们可以取数组c[i] = w + dp[ci][0] - dp[ci][1]
然后求一下所有dp[ci][1]的和记为sum
则最大的值dp[i][0]就等于在c数组中取至多k - 1个数的最大值和,这个通过排序就能得到,将c中k - 1个数中大于0的求和即可。

dp[i][1]就是dp[i][0]再补上一种情况,匹配多一个,这时候相当于加上c中第kk个(如果大于0)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5*1e5+2;

vector<pair<int,int> > edge[maxn];
long long dp[maxn][2];

void dfs(int u,int fa, const int &k){
    vector<long long> weights;
    long long sum = 0;
    for(int i = 0;i < edge[u].size(); ++i){
        pair<int,int> e = edge[u][i];
        int v = e.first;
        int w = e.second;

        if(v == fa) continue;
        dfs(v, u, k);
        weights.push_back(w + dp[v][0] - dp[v][1]);
        sum += dp[v][1];
    }
    //leaf
    if(weights.size() == 0){
        dp[u][0] = dp[u][1] = 0;
    }else{
        sort(weights.begin(), weights.end());
        reverse(weights.begin(), weights.end());
        long long max_sum = 0;
        for(int i = 0;i < k - 1 && i < weights.size(); ++i){
            if(weights[i] <= 0) break;
            max_sum += weights[i];
        }
        dp[u][0] = dp[u][1] = max_sum + sum;
        if(weights.size() >= k && weights[k - 1] > 0)
            dp[u][1] += weights[k - 1];

    }
}

int main()
{
    int q,n,k,u,v,w;
    scanf("%d", &q);
    while(q--){
        scanf("%d%d", &n, &k);
        for(int i = 0;i <= n; ++i) edge[i].clear();

        for(int i = 1;i < n; ++i){
            scanf("%d%d%d", &u, &v, &w);
            edge[u].push_back(make_pair(v,w));
            edge[v].push_back(make_pair(u,w));
        }
        if(k == 0) {puts("0"); return 0;}
        dfs(1, -1, k);
        printf("%I64d\n", dp[1][1]);
    }
    return 0;
}

1223F Stack Exterminable Arrays
题意:一个数组称其为Stack Exterminable,如果它按顺序一个个进栈,遇到和粘顶相同的就消去,当所有数都进栈都消光。现给定一个数组,问其中由多少个Stack Exterminable子数组(连续段)。

题解:如果我们能求出以每个位置结束的Stack Exterminable子数组数目,答案就是所有位置数目的和。
我们设dp[i]为以i位置结束的Stack Exterminable子数组数目。
设当前字符为a[i],我们找到前面最近的一个位置j,a[j]=a[i],且j 到i能够消去。则dp[i] = dp[j - 1] + 1。
这是因为以i为结尾的,如果可以消去,那么i肯定是和j消去的。首先i肯定不能够和j后面的消去,不然的话和j的定义矛盾。再者如果i和j前面的消去意味着j和另外一个消掉了,矛盾如下图。(你将进栈和出栈调换一下也可以看出)
在这里插入图片描述
所以问题就变成,对于每个i,我们找和它匹配的j。怎么找?
如果i和j匹配,要么j = i - 1。要么j + 1到i - 1能够匹配。
所以我们很容易能够写出(prev为我们要找的位置j)

			prev = i - 1;
            while(prev >= 0){
                if(a[i] == a[prev]){
                    match[i] = prev;
                    break;
                }
                prev = match[prev] - 1;
            }

就是从i - 1不停往前跳(相当于从后面开始进栈)。但是这样会有大量的跳转,会耗费大量时间。怎么样防止跳转呢? 我们每个位置用一个map来记录。map[i][a]表示从i这个位置往前进行匹配的Stack Exterminable子数组中,前一个为a的位置(a的位置)。则我们从map[i - 1][a[i]]就能得到位置j了。
问题是map[i]怎么求?
map[i] = map[j - 1]再并上map[i][a[i]] = i
但是直接每个位置都求一个map是不行的,需要大量的存储和赋值。我们可以发现每个被后面的匹配过的位置,它是不会再被匹配的。因为按进栈顺序它不会留到后面去。所以这个位置的前一个位置的map就不会再用到,所以我们可以将j - 1位置的map直接给i用。下面是两种做法,一种采用交换,一种记录map的id,累计用到map id(第二个是其他人的代码)

由于每个点只能至多被后面一个位置匹配,所以每个位置最多只能在map中出现一次。所以map的占用空间是O(n)

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

const int N = 3e5 + 1;
int a[N], dp[N];
map<int,int> match[N];

int main(){
    int q,n;
    cin >> q;
    while(q--){
        scanf("%d", &n);
        match[0].clear();
        for(int i = 1;i <= n; ++i) scanf("%d", &a[i]), match[i].clear();

        int cur_match;
        long long ans = 0;
        memset(dp, 0, (n + 1) * sizeof(int) );
        for(int i = 1;i <= n; ++i){
            cur_match = -1;
            if(match[i - 1].count(a[i])){
                cur_match = match[i - 1][a[i]];
                if(cur_match > 0)
                    swap(match[i], match[cur_match - 1]);
            }
            match[i][a[i]] = i;

            if(cur_match > 0) dp[i] = 1 + dp[cur_match - 1];
            ans += dp[i];
        }
        printf("%I64d\n", ans);

    }
    return 0;
}

第二种

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
int q,n,a[300030],id[300030],idnum,last[300030],dp[300030];
long long ans;
map<int,int>mp[300030];
int main()
{
	scanf("%d",&q);
	while(q--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;++i)scanf("%d",&a[i]);
		ans=0;
		memset(last+1,-1,n<<2);
		for(int i=1;i<=n;++i)
		{
			if(a[i]==a[i-1])
			{
				last[i]=i-2;
				if(id[i-2])id[i]=id[i-2];
				else id[i]=++idnum;
				mp[id[i]][a[i-2]]=i-2;
			}
			else if(mp[id[i-1]].count(a[i]))last[i]=mp[id[i-1]][a[i]]-1,id[i]=id[last[i]]?id[last[i]]:++idnum,mp[id[i]][a[last[i]]]=last[i];
			if(~last[i])dp[i]=dp[last[i]]+1,ans+=dp[i];
		//	printf("%d %d %d\n",last[i],dp[i],id[i]);
		}
		printf("%lld\n",ans);
		memset(dp+1,0,n<<2);
		memset(id+1,0,n<<2);
		for(int i=1;i<=idnum;++i)mp[i].clear();idnum=0;
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章