NOIP2012复盘
D1T1 P1079 Vigenère 密码
只要把A到Z换成0到25,那么这个运算就变成了一个膜为26的加法了。
记得不够的时候将\(k\)重复使用即可。
代码:
#include
using namespace std;
string key, str;
char cal(char k, char s)
{
if(k >= 'A' && k <= 'Z') k += 32;
if(s >= 'A' && s <= 'Z') s += 32;
char ans = s - k + 'a';
while(ans < 'a') ans += 26;
while(ans > 'z') ans -= 26;
return ans;
}
int main()
{
cin >> key >> str;
for(int i = 0; i < str.length(); i++)
{
bool big = str[i] >= 'A' && str[i] <= 'Z';
char temp = cal(key[i % key.length()], str[i]);
if(big) temp -= 32;
cout << temp;
}
cout << endl;
return 0;
}
D1T2 P1080 国王游戏
显然不可以遍历所有的排列获得最优解,不过我们可以拿来对拍。
我们考虑一个国王两个大臣的情况。国王左右手为\(a\)和\(b\),大臣A左右手为\(a_1\)和\(b_1\),大臣B为\(a_2\)和\(b_2\)。
如果是国王 A B这么排的话,A是\(\frac{a}{b_1}\),B是\(\frac{a\times a_1}{b_2}\),\(ans_1\)是两个的最大值。
如果是国王 B A这么排的话,A是\(\frac{a}{b_2}\),B是\(\frac{a\times a_2}{b_1}\),\(ans_2\)是两个的最大值。
可以知道\(\frac{a}{b_1}\leq \frac{a\times a_2}{b_1}\),\(\frac{a}{b_2} \leq \frac{a \times a_1}{b_2}\)。
令\(ans_1
发现推回去也是一样的,这是充要条件。
所以当我们把这两个大臣按\(a_ib_i\)从小到大排序时,会得到更小的答案。
按照这个原理,我们可以随机取任何的两个大臣,按交换位置前后去证明,结果是一样的。
所以结论是:\(a_ib_i\)从小到大排序时,能得到最优解。
套一个高精即可
代码:
#include
#include
#include
const int maxn = 1005;
struct Nodes
{
int l, r;
bool operator < (const Nodes &rhs) const
{
return l * r < rhs.l * rhs.r;
}
} s[maxn];
int n;
int L, R;
struct INT
{
int a[10005], len;
INT()
{
memset(a, 0, sizeof a);
len = 0;
}
void init(int x)
{
if(x == 0) len = 1;
else
{
while(x)
{
a[len++] = x % 10;
x /= 10;
}
}
}
bool operator < (const INT &rhs) const
{
if(len != rhs.len) return len < rhs.len;
for(int i = len - 1; i >= 0; i--)
{
if(a[i] < rhs.a[i]) return true;
else if(a[i] > rhs.a[i]) return false;
}
return false;
}
INT operator * (const int &rhs) const
{
INT ret;
for(int i = 0; i < len; i++) ret.a[i] = a[i] * rhs;
int llen;
for(int i = 0; i < len || ret.a[i]; i++)
{
if(ret.a[i] / 10)
{
ret.a[i + 1] += ret.a[i] / 10;
ret.a[i] %= 10;
}
llen = i;
}
if(ret.a[llen] == 0) ret.len = llen;
else ret.len = llen + 1;
return ret;
}
INT operator / (const int &x) const
{
INT ret;
ret.len = len;
int rest = 0;
for(int i = len - 1; i >= 0; i--)
{
rest = rest * 10 + a[i];
ret.a[i] = rest / x;
rest %= x;
}
while(ret.len > 1 && ret.a[ret.len - 1] == 0) ret.len--;
return ret;
}
void print()
{
for(int i = len - 1; i >= 0; i--) printf("%d", a[i]);
printf("\n");
}
};
int main()
{
/*
while(233)
{
int x, y; scanf("%d%d", &x, &y);
INT xx; xx.init(x);
INT yy; yy.init(y);
INT res1 = xx * y, res2 = xx / y;
res1.print();
res2.print();
printf("%d\n", xx < yy);
}
return 0;
*/
scanf("%d%d%d", &n, &L, &R);
for(int i = 1; i <= n; i++) scanf("%d%d", &s[i].l, &s[i].r);
std::sort(s + 1, s + n + 1);
INT temp; temp.init(L);
INT ans;
for(int i = 1; i <= n; i++)
{
ans = std::max(ans, temp / s[i].r);
temp = temp * s[i].l;
}
ans.print();
return 0;
}
D1T3 P1081 开车旅行
这道题暴力挺好写的,但是正解的倍增做法很难。
首先应该预处理出小A和小B在每个城市的下一个城市是哪个,也就是去找后面的最小点和次小点。
预处理有两种方法,第一种是建一颗以海拔为关键字的平衡树,依次插入找前驱后继即可。
第二种是离散化之后拿坐标去建双向链表,用很多次判断去找到最小点和次小点。
预处理之后设\(f[i][j]\)为从第\(i\)座城市开始,A和B每人都开了\(2^j\)天所到的点。
设\(dpa[i][j]\)和\(dpb[i][j]\)为上述路程中A B走的路程。
上面三个数组的递推都很简单,但是初始化不简单,需要注意。
第一问就可以遍历所有的起点城市,总共用\(O(n \log n)\)的复杂度回答。
第二问直接可以通过倍增从大到小慢慢凑,就可以求出路程总数。
代码细节很多,很不好写:
#include
const int maxn = 100005;
int n, m, l, r, j;
struct Nodes {
int val, idx, left, right;
bool operator < (const Nodes &rhs) const {
return val < rhs.val;
}
} d[maxn];
int p[maxn];
int dpa[maxn][21], dpb[maxn][21], f[maxn][21];
int na[maxn], nb[maxn], a, b, ans = n;
double minv = 2147483647;
bool zuo() {
if(!l) return false;
if(!r) return true;
return d[j].val - d[l].val <= d[r].val - d[j].val;
}
int pd(int a, int b) {
if(!a) return d[b].idx;
if(!b) return d[a].idx;
if(d[j].val - d[a].val <= d[b].val - d[j].val) return d[a].idx;
else return d[b].idx;
}
void init() {
int i, j;
for(j = 1; j <= 20; j++) {
for(int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
dpa[i][j] = dpa[i][j - 1] + dpa[f[i][j - 1]][j - 1];
dpb[i][j] = dpb[i][j - 1] + dpb[f[i][j - 1]][j - 1];
}
}
}
void solve(int s, long long x) {
int i, j;
a = b = 0;
for(i = 20; i >= 0; i--) {
if(f[s][i] && 0ll + a + b + dpa[s][i] + dpb[s][i] <= x) {
a += dpa[s][i]; b += dpb[s][i];
s = f[s][i];
}
}
if(na[s] && a + b + dpa[s][0] <= x) a += dpa[s][0];
}
int main() {
int i; long long x;
scanf("%d", &n);
for(i = 1; i <= n; i++) scanf("%d", &d[i].val), d[i].idx = i;
std::sort(d + 1, d + n + 1);
for(i = 1; i <= n; i++) {
p[d[i].idx] = i; d[i].left = i - 1; d[i].right = i + 1;
}
d[1].left = d[n].right = 0;
for(i = 1; i <= n; i++) {
j = p[i]; l = d[j].left; r = d[j].right;
if(zuo()) nb[i] = d[l].idx, na[i] = pd(d[l].left, r);
else nb[i] = d[r].idx, na[i] = pd(l, d[r].right);
if(l) d[l].right = r;
if(r) d[r].left = l;
}
for(i = 1; i <= n; i++) {
f[i][0] = nb[na[i]];
dpa[i][0] = abs(d[p[i]].val - d[p[na[i]]].val);
dpb[i][0] = abs(d[p[f[i][0]]].val - d[p[na[i]]].val);
}
init();
scanf("%lld %d", &x, &m);
for(i = 1; i <= n; i++) {
solve(i, x);
if(b && 1.0 * a / b < minv) {
minv = 1.0 * a / b;
ans = i;
}
}
printf("%d\n", ans);
for(i = 1; i <= m; i++) {
scanf("%d %lld", &j, &x);
solve(j, x);
printf("%d %d\n", a, b);
}
return 0;
}
D2T1 P1082 同余方程
其实这就是求逆元的模板题啊!(题目保证有解)
同余方程化成不定方程就是\(ax-1=kb\),也就是\(ax-kb=1\)
exgcd求的是\(ax+by=m\),这是最标准的方程形式。
这个方程有解,当且仅当\(m \mod gcd(a,b)=0\)。
而因为此处\(m=1\),所以\(gcd(a,b)=1\),即\(ab\)互质。
所以直接按照最标准的exgcd跑一跑,找到那个正的最小的\(x\)即可。
#include
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x = 1;
y = 0;
return a;
}
int r = exgcd(b, a %b, x, y);
int t = x;
x = y;
y = t - a / b * y;
return r;
}
int inverse(int a, int b)
{
int x, y;
exgcd(a, b, x, y);
return (x + b) % b;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", inverse(a, b));
return 0;
}
D2T2 P1083 借教室
这道题比D1T2简单多了好吧
我们把这些教室看成一个大区间,那么就变成区间上的操作了。
第一种做法是直接建出线段树,每次借教室直接区间减,当询问到最小值小于0时就停止。
这种做法常数大,远古老爷机一定跑不过的。
第二种是利用题目的单调性进行二分答案,用差分来检验答案。
题目告诉我们一旦有不满足的立即停止,而满足越多越容易不满足。所以就可以二分答案了。
check函数中对每一次借教室只需要前端+1,后端再后的位置-1即可。最后跑一次前缀和就完事了。
#include
#include
using namespace std;
const int maxn = 1000005;
int a[maxn], diff[maxn];
struct Node
{
int u, v, w;
} s[maxn];
int n, m;
int read()
{
int ans = 0, s = 1;
char ch = getchar();
while(ch > '9' || ch < '0')
{
if(ch == '-') s = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ans = ans * 10 + ch - '0';
ch = getchar();
}
return s * ans;
}
bool check(int mid)
{
memset(diff, 0, sizeof(diff));
for(int i = 1; i <= mid; i++)
{
diff[s[i].u] += s[i].w;
diff[s[i].v + 1] -= s[i].w;
}
for(int i = 1; i <= n; i++)
{
diff[i] += diff[i - 1];
if(diff[i] > a[i]) return false;
}
return true;
}
int main()
{
n = read(), m = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= m; i++)
{
s[i].w = read(), s[i].u = read(), s[i].v = read();
}
if(check(m))
{
printf("0\n");
return 0;
}
int left = 1, right = m, ans = 0;
while(left <= right)
{
int mid = (left + right) >> 1;
if(check(mid)) ans = mid, left = mid + 1;
else right = mid - 1;
}
printf("-1\n%d\n", ans + 1);
return 0;
}
D2T3 P1084 疫情控制
https://www.luogu.org/blog/qzh/p1084-yi-qing-kong-zhi
#include
const int maxn = 100005;
int n, m;
struct Edges {
int next, to, weight;
} e[maxn];
int head[maxn], tot;
int query[maxn], qtot;
int fa[maxn][21], dist[maxn][21];
int read() {
int ans = 0, s = 1;
char ch = getchar();
while(ch > '9' || ch < '0') {
if(ch == '-') s = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
ans = ans * 10 + ch - '0';
ch = getchar();
}
return s * ans;
}
void link(int u, int v, int w) {
e[++tot] = (Edges){head[u], v, w};
head[u] = tot;
}
void dfs1(int u, int f, int dis) {
fa[u][0] = f; dist[u][0] = dis;
for(int j = 1; j <= 20; j++) {
fa[u][j] = fa[fa[u][j - 1]][j - 1];
dist[u][j] = dist[u][j - 1] + dist[fa[u][j - 1]][j - 1];
}
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v == f) continue;
dfs1(v, u, e[i].weight);
}
}
struct Nodes {
int val, idx;
bool operator < (const Nodes &rhs) const {
return val < rhs.val;
}
} s[maxn];//¿ÉÒÔÖ§Ô®±ðÈ˵Ä
int stot;
bool vis[maxn];// ÒѾפÔúÁËÂð
bool need[maxn];
int a[maxn], atot;
int b[maxn], btot;
bool dfs(int u) {
bool isleaf = true;
if(vis[u]) return true;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v == fa[u][0]) continue;
isleaf = false;
if(!dfs(v)) return false;
}
if(isleaf) return false;
return true;
}
bool check(int mid) {
// init
memset(vis, false, sizeof vis);
memset(need, false, sizeof need);
stot = atot = btot = 0;
for(int i = 1; i <= m; i++) {
int now = query[i], dis = 0;
for(int j = 20; j >= 0; j--) {
if(fa[now][j] > 1 && dis + dist[now][j] <= mid) {
dis += dist[now][j]; now = fa[now][j];
}
}
if(fa[now][0] == 1 && dis + dist[now][0] <= mid) {
s[++stot] = (Nodes){mid - dis - dist[now][0], now};
} else vis[now] = true;
}
for(int i = head[1]; i; i = e[i].next) {
int v = e[i].to;
if(!dfs(v)) need[v] = true;
}
std::sort(s + 1, s + stot + 1);
for(int i = 1; i <= stot; i++) {
if(need[s[i].idx] && s[i].val < dist[s[i].idx][0]) need[s[i].idx] = false;
else a[++atot] = s[i].val;
}
for(int i = head[1]; i; i = e[i].next) {
int v = e[i].to;
if(need[v]) {
b[++btot] = dist[v][0];
}
}
if(atot < btot) return false;
std::sort(a + 1, a + atot + 1);
std::sort(b + 1, b + btot + 1);
int i = 1, j = 1;
while(i <= btot && j <= atot) {
if(a[j] >= b[i]) i++, j++;
else j++;
}
if(i > btot) return true;
return false;
}
int main() {
int left = 0, right = 0, ans = -1;
n = read();
for(int i = 1; i < n; i++) {
int u = read(), v = read(), w = read();
link(u, v, w); link(v, u, w);
right += w;
}
dfs1(1, 0, 0);
m = read();
for(int i = 1; i <= m; i++) query[i] = read();
while(left <= right) {
int mid = (left + right) / 2;
if(check(mid)) ans = mid, right = mid - 1;
else left = mid + 1;
}
printf("%d\n", ans);
return 0;
}