2018-8-25
ccpc-2018网络赛 A buy and resell hdu-6438 http://acm.hdu.edu.cn/showproblem.php?pid=6438 贪心,伪持续化 priority_queue pair
题目大意:有n天,每天都可以进行买入(花费ai),卖出(收益ai-ak),什么都不干三种操作,问可以获得的最大收益和获得最大收益之下的最小交易次数(什么也不做不算交易次数)
初始思路:输入离散的点,贪心,寻找每一个最长上升段,用末尾元素-起始元素作为贡献,直到没有上升段为止。
如果是朴素算法面对一个纯上升序列的数据复杂度O(n^2)gg
算法优化:在线处理,考虑当前状态的最优解,由于每次求上升段用的是起始元素和末尾元素,考虑将数据用上升优先队列来存储,每次输入新的元素,如果队列不为空的话,用其和最高优先度(最小)的元素作比较
贪心,最优的方案一定是买和卖次数相同,我们每次考虑当前点的卖出,然后往前找最优的买入点,同时,最优的方案可能有多个,但是最优的结果只有一个(1,2,3,4这样的数据用1-4,2-3配对和1-3,2-4这样配对产生的收益相同)
如果该元素小于等于最高优先度的元素,那么在这个点卖出不会对最大收益产生任何贡献,将其加入队列
如果大于最高优先度的元素
这个时候关键点来了,当前最高收益不一定是整个结果的最优方案,比如 2 5 7这样的数据,所以想得到最优的方案,必须要对数据的存储进行处理,达到可以反悔的效果,从而得到最优方案
将这个队列的存储元素改为pair fir的值为val,一旦新输入的元素和队首元素配对,pop出当前队首元素 ans+=now-q.top;然后push两次当前元素进队列,分别为{val,1},{val,2} 一旦这个元素成为队首,这样对首就有两个一样的fir值的pair,当前的贪心最优方案一定是和最小的元素配对,如果pair的sec值为1,表示可以反悔,也就是说 贡献改为 当前元素 和 和当前对首配对的老的队首(2333) ,交易次数不变 , 并且当前的对首还存在于队列,ans+=now-q.top();相当于找了一个中间点两次贡献等于这次操作的总贡献
回过头来想,如果没配对成功的话,只push一次,pair的sec值设为2,表示不能反悔。
如果遇到当前对首元素sec值为2,此时的贪心策略仍旧是选择队首元素进行新的一次买卖配对,而不是选择老对首更小的 。
总复杂度为O(nlogn),完美契合总数据 5e5
#include<iostream>
#include<queue>
#include<cstring>
#define ll long long
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)
{
priority_queue < pair<ll,ll>,vector<pair<ll,ll> > ,greater< pair<ll,ll> > > q;
int n,time=0;
ll ans=0;
ll temp;
cin>>n;
while(n--)
{
cin>>temp;
if(!q.empty()&&temp>q.top().first)
{
if(q.top().second==1)//反悔
{
time--;
}
else//反悔的话 time不变,不反悔的话time+=2
{
time++;
}
ans+=temp-q.top().first;//当前最大贡献
time++;
q.pop();
q.push({temp,1});q.push({temp,2});
}
else// 队列为空或者该元素是当前队列中的最小元素
{
q.push({temp,2});
}
}
cout<<ans<<' '<<time<<endl;
}
return 0;
}
codeforces #506_div3-C http://codeforces.com/contest/1029/problem/C 模拟 multiset
题目大意:n个一维线段,给出每个线段的左右端点,问去掉一个线段之后,剩余n-1个线段的最大重合段[l,r] (这n-1个线段都有子集在[l,r]中)
思路:模拟,需要用到删除操作,并且同一个值可能有多个可能会影响结果的数据,开两个multiset存
开一个数组存放每组l,r循环遍历数组,每次删除这个线段, 当前最大重合段就是
min(right)-max(left),最后再把这组数据弄回去
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#define ll long long
using namespace std;
multiset<int>l;
multiset<int>r;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
int L[300005];
int R[300005];
for(int i=1;i<=n;i++)
{
cin>>L[i]>>R[i];
l.insert(L[i]);
r.insert(R[i]);
}
multiset<int>::iterator iter;
ll ans=0;
for(int i=1;i<=n;i++)
{
ll temp;
l.erase(l.find(L[i]));
r.erase(r.find(R[i]));
iter=l.end();iter--;
temp=*r.begin()-*iter;
ans=max(temp,ans);
l.insert(L[i]);
r.insert(R[i]);
}
cout<<ans<<endl;
return 0;
}
codeforces #506_div3-Dhttp://codeforces.com/contest/1029/problem/D 思路转换 模拟 map数组 计数map
题目大意:给定n个数和 k,n个数字任意组合 (123 5变为1235或5123)问有多少种组合满足组合后的数%p==0
两个数字拼凑可以理解成前一个数*后一个数的位数+后一个数
数字最大不超过10^9也就是数不超过9位
暴力,先统计每个数字的位数,把每个数字 *(1 10 100 ... 1e9)在mod k 的数字保存下来,转换为两个数的加法==k
而n只有2e5 开10个map(写的时候map能开数组也是惊了) m[i][j]记录前面*i个10之后在mod k之下 结果为j的数量
复杂度 O(nlogn) 再转化为遍历每个数字的不同num,在map里找有没有对应的k-num 注意这里是加法,不是乘法
#include<iostream>
#include<map>
using namespace std;
typedef long long LL;
const int maxn=200005;
map<int,int> mp[11];
int a[maxn];
int w[maxn];
int n,k,x,c;
int f[11];
int main()
{
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];//存储原始输入的数
x=a[i];
c=0;
while(x)
{
x/=10;
c++;
}
w[i]=c;
mp[c][a[i]%k]++;//mp[i][j]表示这个数字有i位,在 mod k 下余几
}
f[0]=1;
for(int i=1;i<=10;i++)
{
f[i]=10LL*f[i-1]%k;
}
LL ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=10;j++)
{
x=(k-1LL*a[i]*f[j]%k)%k;
if(mp[j].find(x)!=mp[j].end())
{
ans+=mp[j][x];
if(a[i]%k==x&&w[i]==j)
{
ans--;
}
}
}
}
cout<<ans<<endl;
return 0;
}