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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章