[CF1251E2]Voting
壹、题目描述 ¶
贰、题解 ¶
真的是道思维思维好题......可是我是大傻逼。
注意范围 \(10^5\),除了线性 \(\rm DP\) 以外还有就是一些奇奇怪怪的贪心。后效性很强,不考虑 \(\rm DP\),想一想贪心怎么做吧。
一般的贪心思路
既然都贪心了,那么我们先考虑一些贪心行为:
- 如果一个人立场坚定,那么我们更有可能是用钱去打动他;
- 如果一个人立场并不那么坚定,我们更偏向于让他跟随大势;
这些贪心行为的正确性不给证明了,毕竟我也证不来.....
我们考虑按照 \(m\) 从小到大排序,现在,我们考虑所有 \(m=x\) 的人,并假设 \(m\) 小于 \(x\) 的人都已经被打动了,现在我们想让这些 \(m=x\) 的人也来投票。
显然,如果 \(\sum[m_i<x]\ge x\),那么这些人本身就已经被打动了,我们没有什么必要再去考察他们,接下来 \(\sum[m_i<x]\overset \Delta= cnt\)
现在主要考虑一下其他情况,这时,我们必须要收买一些人,这就有俩策略:要么把所有 \(m=x\) 的人都收买掉,要么收买一些最便宜的人,让投票人数达到 \(m\),这样 \(m=x\) 的人就随大势了。注意,前者一定不优于后者,分类讨论一下:
- 如果最便宜的人都在 \(m=x\) 这一堆里面,显然,我们并不需要将所有人都买完,买够 \(x-cnt\) 个人就行了,所以前者一定不优于后者;
- 如果最便宜的人并不都在 \(m=x\) 中,考虑两种策略达成的效果 —— 都是让 \(m=x\) 的人投票,但是前者仅仅只让 \(m=x\) 的人投了票,而后者还买了一些后面立场更坚定,更便宜的人。其实这是一种贪心行为;
按照第二种策略执行就行了,不过若按 \(x\) 从小到大,并不能很好地执行贪心行为,因为在这种顺序下,我们在执行第二种策略的时候,也有可能买到立场不是十分坚定的人,所以按照 \(x\) 由大到小执行。
反悔贪心
我们先贪心地买便宜的人,不过我们可能会遇到一种情况:我们在前面某个时刻用钱打动了 \(x\),在当前这个时刻我们已经有 \(k\) 个支持者了,我们想让 \(y\) 也投票,但是发现 \(k<m_y\),即我们只能砸钱买 \(y\),不过我们又发现,由于我们在当前情形下无论如何都要用钱买 \(y\),并且我们发现,如果我们让 \([1,x-1],[x+1,y-1]\) 这两段中的人的处理策略都不变,那么有 \(k-1\) 个,由于我们无论如何都要花钱收买 \(y\),这样我们又有 \(k\) 个投票者,并且我们发现 \(m_x\le y\),即我们不在曾经收买 \(x\) 的时候收买 \(x\) 而是收买 \(y\),而 \(x\) 又因为大势投靠我们,那为什么不收回 \(x\) 的钱转而买 \(y\),反正 \(y\) 无论如何都要花钱。这样我们甚至可以剩下 \(x\) 的钱。于是,使用 \(\tt multiset\) 模拟即可。
个人以为,这种实质应该是,我们在贪心选择 \(p\) 最小的时候,发现某个人可以将前面的某些人覆盖掉,这个时候我们就可以从这群被覆盖中的人中选出一个当初让我们花费最多的,让当前这个人将其覆盖掉。
叁、参考代码 ¶
# include <bits/stdc++.h>
using namespace std;
// # define NDEBUG
# include <cassert>
namespace Elaina {
# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
# define mmset(a, b) memset(a, b, sizeof (a))
# define mmcpy(a, b) memcpy(a, b, sizeof (a))
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
template <class T> inline T fab(T x) { return x < 0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
inline char fgetc() {
# define BUFFERSIZE 5000
static char BUF[BUFFERSIZE], *p1=BUF, *p2=BUF;
return p1==p2 && (p2=(p1=BUF)+fread(BUF, 1, BUFFERSIZE, stdin), p1==p2)? EOF: *p1++;
}
template <class T> inline T readin(T x) {
x=0; int f=0; char c;
while((c=fgetc())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=fgetc()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template <class T> inline void writc(T x, char s='\n') {
static int fwri_sta[55], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
} using namespace Elaina;
const int maxn=2e5;
struct node { int m, p, id; };
inline void solve() {
int n=readin(1);
vector<node>s(n);
for(int i=0; i<n; ++i) {
s[i].m=readin(1), s[i].p=readin(1);
s[i].id=i;
}
sort(s.begin(), s.end(), [](const node& a, const node& b) { return a.m<b.m; } );
vector<int>done(n, 0);
vector<node>ms(s);
int cur=0, vote=0;
while(cur<n && ms[cur].m<=vote)
done[ms[cur].id]=1, ++vote, ++cur;
sort(s.begin(), s.end(), [](const node& a, const node& b) { return a.p<b.p; } );
multiset<int>repent;
int p=0; ll cost=0;
while(vote<n) {
while(done[s[p].id]) ++p;
done[s[p].id]=-1; // another tag, mean to buy it
cost+=s[p].p, ++vote;
/** before update, the need of the person in @p repent only need current @p vote-1 , so we can perent to choose there person */
if(!repent.empty()) {
cost-=*repent.rbegin();
auto it=repent.end(); --it;
repent.erase(it);
}
while(cur<n && ms[cur].m<=vote) {
if(!done[ms[cur].id]) done[ms[cur].id]=1, ++vote;
else if(!~done[ms[cur].id]) repent.insert(ms[cur].p);
++cur;
}
}
writc(cost);
}
signed main() {
rep(_, 1, readin(1)) solve();
return 0;
}
肆、关键の地方 ¶
这些贪心真的玄学啊......以后遇到这种题,先从分析贪心行为入手吧......