TAG:
筛法、思维 = 大素数区间筛
难度:
★★★
题意:
给定两个整数L,R( 1 < = L < = R < = 2 31 , R − L < = 1 0 6 1<=L<=R<=2^{31}, R-L <= 10^{6} 1<=L<=R<=231,R−L<=106),求闭区间 [ L , R ] [L, R] [L,R]中相邻两个质数的差最大是多少,输出这两个质数。
思路:
《算法竞赛进阶指南》李煜东著 第131页
L,R的范围很大,任何已知算法都无法在规定时间内生成 [ 1 , R ] [1, R] [1,R]间所有质数。但R-L的值很小,并且任何一个合数n必定包含一个不超过 n \sqrt n n个质因子。
所以,我们只需要用筛法求出 [ 2 , R ] [2, \sqrt R] [2,R]之间的所有质数。对于每个质数p,把 [ L , R ] [L, R] [L,R]中能被p整除的数标记,即标记 i ∗ p ( ⌈ L p ⌉ < = i < = ⌊ R p ⌋ ) i*p(\lceil \frac {L}{p}\rceil <=i <=\lfloor \frac {R}{p} \rfloor) i∗p(⌈pL⌉<=i<=⌊pR⌋)为合数。
最终所有未被标记的数就是 [ L , R ] [L, R] [L,R]中的质数。对相邻两质数两两比较,找出差值最大的即可。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int MAX = 65536 + 10;
const int maxn = 1000000 + 10;
bool is_prime[MAX];
vector<int> prime;
void init_prime()
{
memset(is_prime, true, sizeof(is_prime));
is_prime[0] = false;
is_prime[1] = false;
for(int i = 2; i < MAX; i++)
{
if(is_prime[i])
{
prime.push_back(i);
for(int j = i*2; j < MAX; j+=i)
{
is_prime[j] = false;
}
}
}
}
bool f[maxn];
vector<long long> v;
int main()
{
ios::sync_with_stdio(false);
init_prime();
long long l, r;
while(cin >> l >> r)
{
memset(f, true, sizeof(f));
if(l == 1)
{
f[0] = false;
}
long long sqrtr = sqrt((long double)r);
for(int i = 0; prime[i] <= sqrtr; i++)
{
long long pr = prime[i];
for(long long j = ceil((double)l / pr); pr * j <= r; j++)
{
if(j != 1)
{
f[j*pr-l] = false;
}
}
}
long long n = r - l;
v.clear();
long long minn = 1e8;
long long maxx = 0;
pair<long long, long long> minans;
pair<long long, long long> maxans;
for(int i = 0; i <= n; i++)
{
if(f[i])
{
if(!v.empty())
{
if(l+i - v.back() < minn)
{
minn = l+i - v.back();
minans = make_pair(v.back(), l+i);
}
if(l+i - v.back() > maxx)
{
maxx = l+i - v.back();
maxans = make_pair(v.back(), l+i);
}
}
v.push_back(l+i);
}
}
if(v.size() >= 2)
{
cout << minans.first << "," << minans.second << " are closest, " << maxans.first << "," << maxans.second << " are most distant." << endl;
}
else
{
cout << "There are no adjacent primes." << endl;
}
}
return 0;
}
TAG:
差分前缀和 / 树状数组区间修改单点查询 / 线段树
难度:
★★
题意:
有编号 [ 1 , n ] [1, n] [1,n]的n ( n < = 1 0 6 ) (n <= 10^{6}) (n<=106)个位置,初始权值都为零,之后做n次操作,每次把 [ l , , r ] [l, ,r] [l,,r]区间的权值分别+1,最后输出每个位置的权值。
思路:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 100000 + 10;
int C[maxn];
int n;
int main()
{
while(scanf("%d", &n) != EOF && n)
{
memset(C, 0, sizeof(C));
for(int i = 0; i < n; i++)
{
int l, r;
scanf("%d%d", &l, &r);
C[l]++;
C[r+1]--;
}
for(int i = 1; i <= n; i++)
{
C[i] += C[i-1];
if(i != 1)
{
printf(" ");
}
printf("%d", C[i]);
}
cout << endl;
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 100000 + 10;
int C[maxn];
int n;
int lowbit(int x)
{
return x & (-x);
}
void update(int p, int x)
{
while(p <= n)
{
C[p] += x;
p += lowbit(p);
}
}
void update(int l, int r, int x)
{
update(r + 1, -x);
update(l, x);
}
int query(int p)
{
int sum = 0;
while(p > 0)
{
sum += C[p];
p -= lowbit(p);
}
return sum;
}
int main()
{
while(scanf("%d", &n) != EOF && n)
{
memset(C, 0, sizeof(C));
for(int i = 0; i < n; i++)
{
int l, r;
scanf("%d%d", &l, &r);
update(l, r, 1);
}
for(int i = 1; i <= n; i++)
{
if(i != 1)
{
printf(" ");
}
printf("%d", query(i));
}
cout << endl;
}
return 0;
}
TAG:
Floyd算法变形
难度:
★★★
题意:
给出各种货币之间的汇率(注意是单向边),问是否存在种货币经过多次兑换后再兑换成原货币比之前多的情况。
思路:
算法不难,难在想到用Floyd的原理。
跑Floyd,只不过更新条件变成了乘法,最后判断是否有d[i][i] > 1的情况,也就是自己跟自己兑换还变多了。
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 30 + 10;
map<string, int> id;
int n, m;
double d[maxn][maxn];
bool check()
{
for(int i = 0; i < n; i++)
{
if(d[i][i] > 1)
{
return true;
}
}
return false;
}
int main()
{
int cases = 1;
while(cin >> n && n != 0)
{
id.clear();
memset(d, 0, sizeof(d));
for(int i = 0; i < n; i++)
{
string s;
cin >> s;
id[s] = i;
}
cin >> m;
while(m--)
{
string a, b;
double v;
cin >> a >> v >> b;
d[id[a]][id[b]] = v;
}
for(int k = 0; k < n; k++)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(d[i][k] * d[k][j] > d[i][j])
{
d[i][j] = d[i][k] * d[k][j];
}
}
}
}
printf("Case %d: %s\n", cases++, check() ? "Yes" : "No");
}
return 0;
}
TAG:
数据结构、对顶栈
难度:
★★★★
题意:
维护一个整数序列的编辑器,有以下五种操作,操作总数不超过 1 0 6 10^{6} 106。
如果移动或者删除的操作出边界跳过即可
思路:
对顶栈这个数据结构很有意思,如果没接触过的话很难想到。
本题所有的修改操作都是在序列中间的某个指定位置进行修改的,具体来说I、D、L、R四种操作都在光标位置处发生,并且操作完成后光标至多移动一个位置。
对此我们可以建立两个栈,st1存储光标左边的序列,光标近紧邻的前一个数据在栈顶;st2存储光标右边的序列,光标近紧邻的后一个数据在栈顶。这两个栈合起来就是整个序列。因为查询操作的k不超过光标位置,所以我们找个数组维护st1栈前缀和的最大值就行了。
具体操作见代码注释
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1000000 + 10;
const long long INF = 1e15 + 10;
stack<long long> st1, st2;
// sum前缀和数组,maxx储存前缀和的最大值
long long sum[maxn], maxx[maxn];
int main()
{
ios::sync_with_stdio(false);
int Q;
while(cin >> Q)
{
while(!st1.empty()) st1.pop();
while(!st2.empty()) st2.pop();
sum[0] = sum[1] = 0;
maxx[0] = maxx[1] = -INF;
// k是光标位置
int k = 1;
while(Q--)
{
char op;
cin >> op;
// 插入操作,把插入元素放到第一个栈中,再更新sum和maxx
if(op == 'I')
{
long long x;
cin >> x;
st1.push(x);
sum[k] = sum[k-1] + x;
maxx[k] = max(maxx[k-1], sum[k]);
k++;
}
// 删除操作,删除第一个栈栈顶元素
else if(op == 'D')
{
if(st1.empty()) continue;
st1.pop();
k--;
}
// 左移光标,把第一个栈的栈顶元素移动到第二个栈的栈顶
else if(op == 'L')
{
if(st1.empty()) continue;
st2.push(st1.top());
st1.pop();
k--;
}
// 右移光标,把第二个栈的栈顶元素移动到第一个栈的栈顶
else if(op == 'R')
{
if(st2.empty()) continue;
sum[k] = sum[k-1] + st2.top();
maxx[k] = max(maxx[k-1], sum[k]);
k++;
st1.push(st2.top());
st2.pop();
}
// 查询操作,直接查询maxx数组即可
else // if(op == 'Q')
{
int tk;
cin >> tk;
cout << maxx[tk] << endl;
}
}
}
return 0;
}
TAG:
贪心
难度:
★★
题意:
有n ( n < = 5 ∗ 1 0 4 ) (n<=5*10^{4}) (n<=5∗104)头牛在蓄栏中吃草。每个蓄栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个蓄栏。给定n头牛和每头牛开始和结束吃草时间,在这段时间牛会一直吃草,求需要的最小蓄栏数目和每头牛对应的蓄栏方案。如有多种方案,输出一种可行方案即可。
思路:
对牛按吃草开始时间递增排序,每次找一个空闲时刻和开始时刻离得最近的蓄栏把牛安排进去;如果没有开始时刻没有空闲的蓄栏,就增加一个蓄栏。这样就能保证蓄栏尽量不被浪费。
至于怎么高效的找小于开始时刻又最接近的蓄栏,我们可以开一个multiset,里面维护蓄栏的结构体,把时间取相反数,也就是负数,然后upper_bound在里面二分即可。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
struct Cow
{
int l, r, id;
int ans;
Cow(int id, int l, int r): id(id), l(l), r(r) {}
bool operator < (const Cow &t) const
{
return l == t.l ? r < t.r : l < t.l;
}
};
struct Stall
{
int id, time;
Stall(int id, int time): id(id), time(time) {}
bool operator < (const Stall &t) const
{
return time < t.time;
}
};
bool cmp(Cow t1, Cow t2)
{
return t1.id < t2.id;
}
multiset<Stall> st;
vector<Cow> v;
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
int l, r;
scanf("%d%d", &l, &r);
v.push_back(Cow(i, l, r));
}
sort(v.begin(), v.end());
for(int i = 0; i < v.size(); i++)
{
multiset<Stall>::iterator it = st.upper_bound(Stall(0, -v[i].l));
if(it == st.end())
{
v[i].ans = st.size()+1;
st.insert(Stall(st.size()+1, -v[i].r));
}
else
{
Stall t = *it;
t.time = -v[i].r;
v[i].ans = t.id;
st.erase(it);
st.insert(t);
}
}
cout << st.size() << endl;
sort(v.begin(), v.end(), cmp);
for(int i = 0; i < n; i++)
{
cout << v[i].ans << endl;
}
return 0;
}
TAG:
构造、签到题
难度:
★
题意:
输入一个数k,要求输出一个不超过 1 0 18 10^{18} 1018的数,让这个数所有“环”的数量等于k,如果没有这样的数输出-1。
环是指十进制阿拉伯数字封闭区域的数量,比如0、4、6有一个环,8有两个环。
思路:
一个个位数最多有两个环,那k大于36的时候肯定构造不出来小于等于 1 0 18 10^{18} 1018的数,输出-1;
其余情况尽量输出8,还剩一个的环的时候输出个4或者6就行了。注意避开用0,因为这样处理不好可能会出前导零。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
if(n > 36)
{
printf("-1");
}
else
{
while(n > 1)
{
printf("8");
n -= 2;
}
if(n == 1)
{
printf("6");
}
}
return 0;
}