Practice IV
前言
七夕过了,点首悲伤的曲子衬托一下我这只单身狗的悲凉 T^T
另外本篇刷题记又名为“填坑记”
Comma Sprinkler - Kattis - comma
题意
给定句子,要求某个单词如果之前出现逗号,则在句中所有的这个单词之前都要加上逗号,如果之后出现逗号,那么所有这个单词之后都要加上逗号,重复这个过程,直到没有新的逗号加入为止
思路 —— 搜索
填四个月前的坑
本题应该是今年ICPC World Final里面最简单的一道题目
首先对输入进来的单词进行离散化处理,便于起到类似Hash的效果
然后就是搜索了,个人采用了BFS,用dir代表向前打逗号或向后打逗号,再不断更新入队列
另外注意used数组(标记已入过队列的数组)需要开二维,因为可能对于同一个单词可能向前打逗号也可能向后打逗号
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N = 1e6 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
struct Node{
int val, dir;
Node(int pval, int pdir): val(pval), dir(pdir) {}
};
char s[N];
int dot[N];
bool used[N][2];
vector<int> vec[N];
string in[N];
string mp[N];
int pin, pmp;
queue<Node> que;
int getIdx(string& s){
return lower_bound(mp, mp + pmp, s) - mp;
}
void bfs(){
memset(used, false, sizeof(used));
for(int i = 0; i < pin; i++){
if(dot[i] == 1){
que.push(Node(getIdx(in[i]), 1));
que.push(Node(getIdx(in[i + 1]), 0));
used[getIdx(in[i])][1] = true;
used[getIdx(in[i + 1])][0] = true;
}
}
while(!que.empty()){
Node u = que.front();
que.pop();
for(int j = 0; j < vec[u.val].size(); j++){
int k = vec[u.val][j];
if(u.dir == 0 && k != 0 && dot[k - 1] == 0){
dot[k - 1] = 1;
int preval = getIdx(in[k - 1]);
if(used[preval][1] == false){
que.push(Node(preval, 1));
used[preval][1] = true;
}
}else if(u.dir == 1 && k != pin - 1 && dot[k] == 0){
dot[k] = 1;
int sucval = getIdx(in[k + 1]);
if(used[sucval][0] == false){
que.push(Node(sucval, 0));
used[sucval][0] = true;
}
}
}
}
}
int main(){
pin = pmp = 0;
while(~scanf("%s", s)){
int len = strlen(s);
if(s[len - 1] == ',') { dot[pin] = 1; s[len - 1] = 0; }
else if(s[len - 1] == '.') { dot[pin] = 2; s[len - 1] = 0; }
else dot[pin] = 0;
in[pin++] = s;
mp[pmp++] = s;
}
sort(mp, mp + pmp);
pmp = unique(mp, mp + pmp) - mp;
for(int i = 0; i < pin; i++){
int pos = getIdx(in[i]);
vec[pos].push_back(i);
}
bfs();
for(int i = 0; i < pin; i++){
printf("%s", in[i].c_str());
if(dot[i] == 1) putchar(',');
if(dot[i] == 2) putchar('.');
if(i < pin - 1) putchar(' ');
}
puts("");
}
Queue - CodeForces - 141C
题意
已知每个人前面有多少个人比它高,要求构造一个符合已知的身高序列,无法构造输出-1
思路
填三个月前的坑
看到dalao的题解想了很久才懂,跪了 orz
按每个人前面有多少个人比它高升序排序,并记为h[i](i为新的下标)
那么每个人前面有h[i]个人比它高,那么就有 i - h[i] - 1个人比它矮(假设身高各不相同)
这样子从左往右,对第i个人,设其身高为 i - h[i],那么自然会有 i - h[i] - 1个比它矮,但是可能会有 h[i] - 1 个人比它高,这时候说明有一个人与 i - h[i] 同高,于是可以将 1 到 i - 1 下标中 >= i - h[i] 的都 +1,这样做的正确性在于,对于任意 1 到 i 的身高序列,都存在 1 到 i 的身高而且有且仅有一个,因此不会影响计算后面 i + 1, i + 2, … 的结果
这个构造方法真是太神奇了!
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N = 3000 + 15;
const int M = 5133 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
struct Node{
string name;
int h;
int ans;
bool operator < (const Node& b) const { return h < b.h; }
};
Node a[N];
char s[N];
int main(){
int n;
while(~scanf("%d", &n)){
for(int i = 1; i <= n; i++){
scanf("%s%d", s, &a[i].h);
a[i].name = s;
}
sort(a + 1, a + n + 1);
bool flag = true;
for(int i = 1; i <= n; i++){
if(i <= a[i].h){
flag = false;
break;
}
}
if(flag){
for(int i = 1; i <= n; i++){
a[i].ans = i - a[i].h;
for(int j = 1; j < i; j++){
if(a[j].ans >= a[i].ans) a[j].ans++;
}
}
for(int i = 1; i <= n; i++){
printf("%s %d\n", a[i].name.c_str(), a[i].ans);
}
}else{
puts("-1");
}
}
}
Maximal GCD - CodeForces - 803C
题意
给定n和k,要求构造一个严格递增的序列,其个数为k,和为n,并且gcd最大,无法构造输出-1
思路
补三个月前的坑
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 15;
typedef long long ll;
bool used[N];
int prime[N];
int pp;
void getPrime(){
memset(used, true, sizeof(used));
pp = 0;
for(int i = 2; i < N; i++){
if(used[i]){
prime[pp++] = i;
}
for(int j = 0; i*prime[j] < N; j++){
used[i*prime[j]] = false;
if(i%prime[j] == 0){
break;
}
}
}
}
ll getMaxn(ll n, ll lval){
ll ret = 1;
for(ll i = 1; i*i <= n; i++){
if(n%i == 0){
if(i >= lval) ret = max(ret, n/i);
if(n/i >= lval) ret = max(ret, i);
}
}
return ret;
}
int main(){
getPrime();
ll n, k;
while(~scanf("%lld%lld", &n, &k)){
if((double)(k + 1) > (double)(2 * n/k)){
puts("-1");
continue;
}
ll lval = (k + 1) * k/2;
ll time = getMaxn(n, lval);
n /= time;
for(ll i = 1; i <= k; i++){
if(i < k) printf("%lld ", i*time);
else printf("%lld\n", time*(n - (k - 1) * k/2));
}
}
}
Planning - CodeForces - 853A
题意
给定序列n个整数和k,现在将整个序列向右偏移k个单位,你可以调整序列的顺序,但每一个数都不能调整到它原位置之前的位置,请你计算sum((p2 - p1) * a[i])的最小值,其中p2为现在的位置,p1为原位置
思路
贪心思想
用优先队列优先处理大的数,使每一个数移动到最邻近自身且没有被占用的位置( >= 自身)
那要怎样才能知道第一个没被占用的位置呢,用并查集维护就可以了,对于每个数被占用后,直接并到它右边一个数所在的并查集即可,对于每一个要移动到的位置则查询其并查集的父节点即可
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <functional>
using namespace std;
typedef long long ll;
const int N = 600000 + 15;
struct Node{
int val, idx;
Node(int pval, int pidx): val(pval), idx(pidx) { }
bool operator < (const Node& b) const {
return val < b.val;
}
};
int ans[N];
int ft[N];
priority_queue<Node> que;
inline void init(){
for(int i = 1; i <= N - 1; i++) ft[i] = i;
}
int find(int x){
return x == ft[x] ? x : ft[x] = find(ft[x]);
}
void merge(int x, int y){
int p = find(x), q = find(y);
if(p != q) ft[q] = p;
}
int main(){
int n, k;
while(~scanf("%d%d", &n, &k)){
init();
ll sum = 0;
int tmp;
for(int i = 1; i <= n; i++){
scanf("%d", &tmp);
que.push(Node(tmp, i));
}
while(!que.empty()){
Node u = que.top();
que.pop();
int pos = max(k + 1, u.idx);
pos = find(pos);
merge(pos + 1, pos);
ans[u.idx] = pos;
sum += (ll)u.val*(ll)(pos - u.idx);
}
printf("%lld\n", sum);
for(int i = 1; i <= n; i++){
printf("%d ", ans[i]);
}
puts("");
}
return 0;
}