前綴和,枚舉+遞推(Average,HDU 5353)

有n個節點,1<=n<=1e5,成環,每個節點有一些物品,相鄰節點可以傳遞一次物品,1和n相鄰。

瞬間就想起了

Money Transfers


這類問題一般都可以轉化爲環的切割,如何求出切割呢?

可以求一個前綴和,如果sum[j]==sum[i](i<j)那麼[i+1,j]就可以切割成一段,首尾剩下的也可以併成一段。

我們會找到很多個sum的值,每一種值對應一個切割方法,值所對應的的多個節點就都是切割點。


在本題我們就通過前綴和找到所有切割,然後對於每一段,可以直接貪心模擬看看是否ok。


看了下官方題解,是枚舉第一個人的操作,然後遞推出其他人的操作。

因爲環狀的問題要同時兼顧兩邊,所以沒有什麼簡單的算法可以得到答案。

但是如果能在環上切一刀,那麼問題就變成了一個線段上的問題,這個時候問題會大大化簡,我們甚至可以直接貪心遞推來得到答案。

即枚舉+遞推。


本題代碼

#include<stdio.h>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100010;

int n;
int a[maxn<<1];
int sum[maxn];
ll SUM;
map<int,int>MAP;
int cnt;
vector<int>vec[maxn];
vector<int>ans;

void read()
{
    MAP.clear();
    cnt=0;
    SUM=0;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",a+i);
        SUM+=a[i];
    }
}

int id(int x)
{
    if(!MAP.count(x))
    {
        MAP[x]=++cnt;
        vec[cnt].clear();
    }
    return MAP[x];
}

bool OK(int l,int r)
{
    if(r-l<=1) return true;
    int jin=0;
    for(int k=l+1;k<=r;k++)
    {
        int x = a[k]+jin;
        if(x>=2||x<=-2) return false;
        if(x==1)
        {
            ans.push_back(k);
            ans.push_back(k+1);
        }
        else if(x==-1)
        {
            ans.push_back(k+1);
            ans.push_back(k);
        }
        jin=x;
    }
    return !jin;
}

bool ok(int x)
{
    ans.clear();
    for(int i=0;i<(int)vec[x].size()-1;i++)
        if(!OK(vec[x][i],vec[x][i+1]))
            return false;
    if(!OK(vec[x][(int)vec[x].size()-1],vec[x][0]+n))
        return false;
    return true;
}

void print()
{
    puts("YES");
    printf("%d\n",ans.size()/2);
    for(int i=0;i<(int)ans.size();i+=2)
        printf("%d %d\n",ans[i]%n+1,ans[i+1]%n+1);
}

int solve()
{
    read();
    if(SUM%n) return 0*puts("NO");
    SUM/=n;
    for(int i=0;i<n;i++)
    {
        a[i]-=SUM;
        if(a[i]>2||a[i]<-2) return 0*puts("NO");
        a[i+n]=a[i];
        sum[i]=a[i];
        if(i) sum[i]+=sum[i-1];
    }
    for(int i=0;i<n;i++)
        vec[id(sum[i])].push_back(i);
    for(int i=1;i<=cnt;i++)
        if(ok(i))
        {
            print();
            return 0;
        }
    puts("NO");
    return 0;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}

Money Transfers代碼

#include<stdio.h>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 100010;

int n;
ll sum[maxn];
map<ll,int>cnt;

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%lld",sum+i);
    for(int i=1;i<n;i++) sum[i]+=sum[i-1];
    for(int i=0;i<n;i++) cnt[sum[i]]++;
    int MAX=0;
    for(map<ll,int>::iterator it = cnt.begin();it!=cnt.end();++it) MAX=max(MAX,it->second);
    printf("%d\n",n-MAX);
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章