@RabbitHu
2017-09-02T14:16:05.000000Z
字数 77072
阅读 2591
日记
有这样的一个集合,集合中的元素个数由给定的N决定,集合的元素为N个不同的正整数,一旦集合中的两个数x,y满足y = P*x,那么就认为x,y这两个数是互斥的,现在想知道给定的一个集合的最大子集满足两两之间不互斥。
输入有多组数据,每组第一行给定两个数N和P(1<=N<=10^5, 1<=P<=10^9)。接下来一行包含N个不同正整数ai(1<=ai<=10^9)。
输出一行表示最大的满足要求的子集的元素个数。
可以多搞几个模数用来取模。
@ 逻辑或打成逻辑与……没想清楚就写。
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll HA = 19260817, ME = 20010313, MO = 97797977;
bool ha[HA], me[ME], mo[MO];
ll n, p, a[100005], ans;
int main(){
scanf("%lld%lld", &n, &p);
for(int i = 0; i < n; i++)
scanf("%lld", &a[i]);
sort(a, a + n);
for(int i = 0; i < n; i++)
if(!ha[a[i]%HA] || !me[a[i]%ME] || !mo[a[i]%MO]){
ans++;
ha[a[i]*p%HA] = me[a[i]*p%ME] = mo[a[i]*p%MO] = 1;
}
printf("%lld\n", ans);
return 0;
}
给出一个地图,其中'*'是泥地,'.'是草地,用一些宽度为1的木板覆盖所有泥地,不能覆盖草地,问最少需要多少木板。
题目很简单,在小行星那道题的基础上,将同一行中不连通的两部分拆成两行,列同理。
@ 又把int定义成了bool!!!
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
char mp[55][55];
int x[55][55], y[55][55], xcnt, ycnt, user[1300];
bool conn[1300][1300], used[1300];
int r, c, ans;
bool find(int i){
for(int j = 1; j <= ycnt; j++){
if(used[j] || !conn[i][j]) continue;
used[j] = 1;
if(!user[j] || find(user[j])){
user[j] = i;
return 1;
}
}
return 0;
}
void init(){
memset(user, 0, sizeof(user));
memset(conn, 0, sizeof(conn));
xcnt = ycnt = ans = 0;
}
int main(){
while(~scanf("%d%d", &r, &c)){
init();
for(int i = 1; i <= r; i++)
scanf("%s", mp[i] + 1);
//重新分配行列编号:x行y列
for(int i = 1; i <= r; i++){
int connecting = 0;
for(int j = 1; j <= c; j++){
if(mp[i][j] == '.') connecting = 0;
else if(connecting) x[i][j] = xcnt;
else connecting = 1, x[i][j] = ++xcnt;
}
}
for(int j = 1; j <= c; j++){
int connecting = 0;
for(int i = 1; i <= r; i++){
if(mp[i][j] == '.') connecting = 0;
else if(connecting) y[i][j] = ycnt;
else connecting = 1, y[i][j] = ++ycnt;
}
}
//二分图连边
for(int i = 1; i <= r; i++)
for(int j = 1; j <= c; j++)
if(mp[i][j] == '*')
conn[x[i][j]][y[i][j]] = 1;
//二分图匹配
for(int i = 1; i <= xcnt; i++){
memset(used, 0, sizeof(used));
ans += find(i);
}
printf("%d\n", ans);
}
return 0;
}
皮尔是一个出了名的盗画者,他经过数月的精心准备,打算到艺术馆盗画。艺术馆的结构,每条走廊要么分叉为二条走廊,要么通向一个展览室。皮尔知道每个展室里藏画的数量,并且他精确地测量了通过每条走廊的时间,由于经验老道,他拿下一副画需要5秒的时间。你的任务是设计一个程序,计算在警察赶来之前(警察到达时皮尔回到了入口也算),他最多能偷到多少幅画。
由于这道题的神奇的输入方式,可以直接边DFS边计算,极大地缩短了代码。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 105, MAXT = 605;
int T, ncnt, dp[MAXN][MAXT];
void dfs(int n){
int cost, value;
scanf("%d%d", &cost, &value);
cost <<= 1;
if(value)
for(int i = cost; i <= T; i++)
dp[n][i] = min(value, (i - cost) / 5);
else{
int ls, rs;
dfs(ls = ++ncnt);
dfs(rs = ++ncnt);
for(int i = cost; i <= T; i++)
for(int j = 0; j <= i - cost; j++)
dp[n][i] = max(dp[n][i], dp[ls][j] + dp[rs][i - cost - j]);
}
}
int main(){
scanf("%d", &T);
dfs(++ncnt);
printf("%d\n", dp[1][T]);
return 0;
}
随手记一下
棵题:给出一个无向图,求割点[1]数量。
~ Tarjan 求割点的主要思路:对于一个u,枚举搜索树上的儿子v,如果其中一个low[v] >= dfn[u](即v不能回溯到u以前的点),则去掉u后,u这棵子树就独立了,所以u是割点。
@ 注意:对于钦定的根节点,需要存在至少两个独立子树v,根节点才是割点。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 105, MAXE = 20005;
int n, ans;
int adj[MAXN], ecnt, go[MAXE], nxt[MAXE];
int dfn[MAXN], low[MAXN], index;
bool iscut[MAXN];
void add(int u, int v){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
bool in(){ //辣鸡输入方式
int u, v;
char c;
scanf("%d", &u);
if(u == 0) return 0;
do{
scanf("%d%c", &v, &c);
add(u, v);
add(v, u);
}while(c != '\n');
return 1;
}
void init(){
ecnt = ans = index = 0;
memset(adj, 0, sizeof(adj));
memset(iscut, 0, sizeof(iscut));
memset(dfn, 0, sizeof(dfn));
}
void tarjan(int u, int pre){
int v, child_cnt = 0;
dfn[u] = low[u] = ++index;
for(int e = adj[u]; e; e = nxt[e])
if(!dfn[v = go[e]]){
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]){
child_cnt++;
iscut[u] = 1;
}
else if(v != pre)
low[u] = min(low[u], dfn[v]);
}
if(u == 1 && child_cnt == 1)
iscut[u] = 0;
}
int main(){
while(1){
scanf("%d", &n);
if(n == 0) break;
init();
while(in());
tarjan(1, 0);
for(int i = 1; i <= n; i++)
if(iscut[i]) ans++;
printf("%d\n", ans);
}
return 0;
}
无向图,求至少添加多少条边可使得任意两点间有至少两条路径?(不同路径的定义是两条路径上所有边都不同,但路径上可以有点相同。)
~ 这道题是求(边)双联通分量,即任意两点间有至少两条(没有重边的)路径的分量。可以利用Tarjan求割边的思路,像Tarjan求强联通分量一样求。求出双联通分量后,所有双联通分量构成了一棵树,只需加边使得树上每一个点都处于环中即可,所以将每对度为1的双联通分量连起来,共需要(度为1的双联通分量数 + 1)/2 条边。
@ 忘打一个"^1"
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXE = 20005, MAXN = 5005;
bool is_bridge[MAXE];
int N, E, ans;
int ecnt = 1, adj[MAXN], nxt[MAXE], go[MAXE];
int dfn[MAXN], low[MAXN], index, cnt = 1, bel[MAXN], du[MAXN];
int stk[MAXN], top;
void add(int u, int v){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
void tarjan(int u, int pre){
int v;
stk[++top] = u;
dfn[u] = low[u] = ++index;
for(int e = adj[u]; e; e = nxt[e]){
if(!dfn[v = go[e]]){
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]){
is_bridge[e] = is_bridge[e^1] = 1;
cnt++;
while(1){
bel[stk[top]] = cnt;
if(stk[top--] == v) break;
}
}
}
else if(v != pre)
low[u] = min(low[u], dfn[v]);
}
}
int main(){
scanf("%d%d", &N, &E);
fill(bel, bel + N + 1, 1); //初始为1,作为根节点所在的双连通分量编号
for(int i = 1; i <= E; i++){
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
tarjan(1, 0);
for(int e = 2; e <= 2*E; e += 2){
if(is_bridge[e])
du[bel[go[e]]]++, du[bel[go[e^1]]]++;
for(int i = 1; i <= cnt; i++)
if(du[i] == 1) ans++;//求出只连着一条边的点的数目
printf("%d\n", (ans + 1)/2);
return 0;
}
给出一个大整数(),求.
~ 昨天爆零了心情不好,写高精度自虐……
@
1. 要开到 , 是不够的……
2. 乘法的进位还是最后一起进的好。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cctype>
using namespace std;
struct bigint {
int l, n[1005];
bigint():l(1){memset(n, 0, sizeof(n));}
bigint(int x):l(0){
memset(n, 0, sizeof(n));
while(x){
n[++l] = x % 10;
x /= 10;
}
}
void in(){
char s[1005];
scanf("%s", s);
l = strlen(s);
for(int i = 1; i <= l; i++)
n[i] = s[l - i] - '0';
}
void out(){
for(int i = l; i; i--)
printf("%d", n[i]);
cout << endl;
}
bool operator < (bigint b) const {
if(l != b.l) return l < b.l;
for(int i = l; i; i--)
if(n[i] != b.n[i])
return n[i] < b.n[i];
return 0;
}
bool operator > (bigint b) const{
if(l != b.l) return l > b.l;
for(int i = l; i; i--)
if(n[i] != b.n[i])
return n[i] > b.n[i];
return 0;
}
bigint operator + (bigint b) {
bigint c;
c.l = max(l, b.l) + 1;
for(int i = 1; i <= c.l; i++){
c.n[i] += n[i] + b.n[i];
c.n[i + 1] += c.n[i] / 10;
c.n[i] %= 10;
}
if(!c.n[c.l]) c.l--;
return c;
}
bigint operator * (bigint b) {
bigint c;
c.l = l + b.l;
for(int i = 1; i <= l; i++)
for(int j = 1; j <= b.l; j++){
c.n[i + j - 1] += n[i] * b.n[j];
}
int last = 0;
for(int i = 1; i <= c.l; i++){
c.n[i] += last;
last = c.n[i] / 10;
c.n[i] %= 10;
}
if(!c.n[c.l]) c.l--;
return c;
}
void haf(){
int last = 0;
for(int i = l; i; i--){
n[i] += last * 10;
last = n[i] & 1;
n[i] >>= 1;
}
while(!n[l]) l--;
}
void operator -- (int why_is_there_a_variable) {
n[1]--;
int x = 1;
while(n[x] < 0) n[x] += 10, n[++x]--;
while(!n[l]) l--;
}
};
bigint tar, l, r, mid, one(1);
int main(){
tar.in();
r.l = 500;
r.n[500] = 1;
while(l < r){
mid = l + r + one;
mid.haf();
bigint tp = mid * mid;
if(tp > tar){
r = mid;
r--;
}
else l = mid;
}
l.out();
return 0;
}
Z小镇是一个景色宜人的地方,吸引来自各地的观光客来此旅游观光。
Z小镇附近共有N(1<N≤500)个景点(编号为1,2,3,…,N),这些景点被M(0<M≤5000)条道路连接着,所有道路都是双向的,两个景点之间可能有多条道路。也许是为了保护该地的旅游资源,Z小镇有个奇怪的规定,就是对于一条给定的公路Ri,任何在该公路上行驶的车辆速度必须为Vi。频繁的改变速度使得游客们很不舒服,因此大家从一个景点前往另一个景点的时候,都希望选择行使过程中最大速度和最小速度的比尽可能小的路线,也就是所谓最舒适的路线。输入描述
第一行包含两个正整数,N和M。
接下来的M行每行包含三个正整数:x,y和v(1≤x,y≤N) 最后一行包含两个正整数s,t,表示想知道从景点s到景点t最大最小速度比最小的路径。s和t不可能相同。输出描述
如果景点s到景点t没有路径,输出“IMPOSSIBLE”。否则输出一个数,表示最小的速度比。如果需要,输出一个既约分数。数据范围
N(1<N≤500), M(0<M≤5000), Vi在int范围内
~ 我好菜啊……这道题一开始理解错了……
实际上,只需要按边枚举,先把边按v从小到大排序,枚举v最小的边,往后的每一条边都取,一边取一边在并查集中合并边的两个端点,直到起点和终点在同一集合中。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 505, MAXM = 5005;
int N, M, s, t, u[MAXM], v[MAXM], w[MAXM];
int fa[MAXN], per[MAXM];
int ansx = 0x3f3f3f3f, ansy = 1;
bool ok = 0;
int findfa(int x){ return x == fa[x] ? x : fa[x] = findfa(fa[x]); }
bool cmp(int a, int b){ return w[a] < w[b];}
int gcd(int a, int b){ return b ? gcd(b, a%b) : a; }
int main(){
scanf("%d%d", &N, &M);
for(int i = 1; i <= M; i++)
scanf("%d%d%d", &u[i], &v[i], &w[i]), per[i] = i;
scanf("%d%d", &s, &t);
sort(per + 1, per + M + 1, cmp);
for(int i = 1; i <= M; i++){
for(int j = 1; j <= N; j++) fa[j] = j;
int x, y = w[per[i]];
for(int j = i; j <= M; j++){
fa[findfa(u[per[j]])] = findfa(v[per[j]]);
if(findfa(s) == findfa(t)){
x = w[per[j]];
if((double)x/y < (double) ansx/ansy)
ok = 1, ansx = x, ansy = y;
break;
}
}
}
int g = gcd(ansx, ansy);
ansx /= g, ansy /= g;
if(!ok) printf("IMPOSSIBLE\n");
else if(ansy == 1) printf("%d\n", ansx);
else printf("%d/%d\n", ansx, ansy);
return 0;
}
n个罪犯中,有些罪犯两两之间有一定仇恨值,如果两个有仇恨值的罪犯关押在同一所监狱中,就会产生和仇恨值大小相等的矛盾。将罪犯分配到两所监狱中,使最大仇恨值最小,输出最小的最大仇恨值。
【数据范围】
对于30%的数据有N≤ 15。
对于70%的数据有N≤ 2000,M≤ 50000。
对于100%的数据有N≤ 20000,M≤ 100000。\
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 20005, MAXM = 100005;
int n, m, ans;
int fa[2*MAXN], u[MAXM], v[MAXM], w[MAXM], per[MAXM];
bool cmp(int a, int b){
return w[a] > w[b];
}
int findfa(int x){
return fa[x] == x ? x : fa[x] = findfa(fa[x]);
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d%d%d", &u[i], &v[i], &w[i]), per[i] = i;
for(int i = 1; i <= 2*n; i++)
fa[i] = i;
sort(per + 1, per + m + 1, cmp);
for(int i = 1; i <= m; i++){
int uu = u[per[i]], vv = v[per[i]], ww = w[per[i]];
if(findfa(uu) == findfa(vv)){
printf("%d\n", ww);
return 0;
}
fa[findfa(uu + n)] = findfa(vv);
fa[findfa(vv + n)] = findfa(uu);
}
printf("0\n");
return 0;
}
题面懒得粘……
(一眼秒23333)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int MAXN = 105;
int n, maxt, pos = 1, ans;
struct mouse{
int t, w;
bool operator < (mouse b) const{
return w < b.w;
}
}m[MAXN];
priority_queue <mouse> e;
bool cmp (mouse a, mouse b) {
return a.t > b.t;
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &m[i].t), maxt = max(maxt, m[i].t);
for(int i = 1; i <= n; i++)
scanf("%d", &m[i].w);
sort(m + 1, m + n + 1, cmp);
for(int i = maxt; i; i--){
while(pos <= n && m[pos].t >= i) e.push(m[pos++]);
if(e.empty()) continue;
ans += e.top().w;
e.pop();
}
printf("%d\n", ans);
return 0;
}
有两个长度为 N 的序列 A 和 B,在 A 和 B 中各任取一个数可以得到 N^2 个和,求这N^2 个和中最小的 N个。
一眼秒(用二分,没用堆什么的),然而……
@ 二分查找检验的时候,达到答案(mid)就要退出循环了, 我居然忘了退出……
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
typedef long long ll;
ll n, a[MAXN], b[MAXN], amax, bmax, l, r, c[3*MAXN], cnt;
int main(){
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), amax = max(amax, a[i]);
for(int i = 1; i <= n; i++)
scanf("%lld", &b[i]), bmax = max(bmax, b[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
l = 0, r = amax + bmax;
while(l < r){
ll mid = (l + r) >> 1, ans = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(a[i] + b[j] > mid || ans > n) break;
ans++;
}
if(a[i] + b[1] > mid || ans > n) break;
}
if(ans >= n) r = mid;
else l = mid + 1;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(a[i] + b[j] >= l) break;
c[++cnt] = a[i] + b[j];
}
if(a[i] + b[1] >= l) break;
}
sort(c + 1, c + cnt + 1);
for(int i = 1; i <= cnt; i++)
printf("%lld ", c[i]);
for(int i = cnt + 1; i <= n; i++)
printf("%lld ", l);
return 0;
}
棵题,但是输入方式非常不人性化……
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cctype>
using namespace std;
const int INF = 0x3f3f3f3f;
int m, num[128], ncnt, dis[60][60], Z;
bool cow[60];
int main(){
for(int i = 1; i <= 52; i++)
for(int j = 1; j <= 52; j++)
dis[i][j] = i == j ? 0 : INF;
scanf("%d", &m);
for(int i = 1; i <= m; i++){
char uc[2], vc[2];
int u, v, w;
scanf("%s%s%d", uc, vc, &w);
u = num[uc[0]] ? num[uc[0]] : num[uc[0]] = ++ncnt;
if(uc[0] == 'Z') Z = u;
else if(isupper(uc[0])) cow[u] = 1;
v = num[vc[0]] ? num[vc[0]] : num[vc[0]] = ++ncnt;
if(vc[0] == 'Z') Z = v;
else if(isupper(vc[0])) cow[v] = 1;
dis[u][v] = dis[v][u] = min(dis[u][v], w);
}
for(int k = 1; k <= ncnt; k++)
for(int i = 1; i <= ncnt; i++)
for(int j = 1; j <= ncnt; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
int minn = INF, ans;
for(int i = 1; i <= ncnt; i++)
if(cow[i] && dis[i][Z] < minn)
minn = dis[i][Z], ans = i;
char ansc;
for(char i = 'A'; i <= 'z'; i++)
if(num[i] == ans) ansc = i;
printf("%c %d\n", ansc, minn);
return 0;
}
随手记一下(二)
一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点u的权值改为t
II. QMAX u v:询问从点u到点v的路径上的节点的最大权值
III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身输入的第一行为一个整数n,表示节点的个数。接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。接下来1行,为一个整数q,表示操作的总数。接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。
对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。
树链剖分详细笔记:传送门
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cctype>
using namespace std;
int read(){
int sgn = 1;
char ch;
while(!isdigit(ch = getchar()))
if(ch == '-') sgn = -1;
int res = ch - '0';
while(isdigit(ch = getchar()))
res = res * 10 + ch - '0';
return sgn * res;
}
const int N = 30005, INF = 0x3f3f3f3f;
int n, Q, val[N];
int ecnt, adj[N], nxt[2*N], go[2*N];
int tot, fa[N], dep[N], top[N], sze[N], son[N], pos[N], idx[N];
int SUM[4*N], MAX[4*N];
void add(int u, int v){
go[++ecnt] = v; nxt[ecnt] = adj[u]; adj[u] = ecnt;
go[++ecnt] = u; nxt[ecnt] = adj[v]; adj[v] = ecnt;
}
void seg_build(int k, int l, int r){
if(l == r) return (void)(SUM[k] = MAX[k] = val[idx[l]]);
int mid = (l + r) >> 1;
seg_build(k << 1, l, mid); seg_build(k << 1 | 1, mid + 1, r);
SUM[k] = SUM[k << 1] + SUM[k << 1 | 1];
MAX[k] = max(MAX[k << 1], MAX[k << 1 | 1]);
}
void seg_change(int k, int l, int r, int p, int x){
if(l == r) return (void)(SUM[k] = MAX[k] = x);
int mid = (l + r) >> 1;
if(p <= mid) seg_change(k << 1, l, mid, p, x);
else seg_change(k << 1 | 1, mid + 1, r, p, x);
SUM[k] = SUM[k << 1] + SUM[k << 1 | 1];
MAX[k] = max(MAX[k << 1], MAX[k << 1 | 1]);
}
int seg_sum(int k, int l, int r, int ql, int qr){
if(l >= ql && r <= qr) return SUM[k];
int mid = (l + r) >> 1, res = 0;
if(ql <= mid) res += seg_sum(k << 1, l, mid, ql, qr);
if(qr > mid) res += seg_sum(k << 1 | 1, mid + 1, r, ql, qr);
return res;
}
int seg_max(int k, int l, int r, int ql, int qr){
if(l >= ql && r <= qr) return MAX[k];
int mid = (l + r) >> 1, res = -INF;
if(ql <= mid) res = max(res, seg_max(k << 1, l, mid, ql, qr));
if(qr > mid) res = max(res, seg_max(k << 1 | 1, mid + 1, r, ql, qr));
return res;
}
void init(){
int q[n], r, u, v;
dep[1] = 1, q[r = 0] = 1;
for(int l = 0; l <= r; l++){
sze[u = q[l]] = 1;
for(int e = adj[u]; e; e = nxt[e]){
if(dep[v = go[e]]) continue;
dep[v] = dep[u] + 1, fa[v] = u;
q[++r] = v;
}
}
for(int l = r; l >= 0; l--){
sze[u = fa[q[l]]] += sze[v = q[l]];
if(sze[v] > sze[son[u]]) son[u] = v;
}
for(int l = 0; l <= r; l++){
if(top[u = q[l]]) continue;
for(int v = u; v; v = son[v])
idx[pos[v] = ++tot] = v, top[v] = u;
}
seg_build(1, 1, n);
}
int path_sum(int u, int v){
if(top[u] != top[v]){
if(dep[top[u]] > dep[top[v]]) swap(u, v);
return seg_sum(1, 1, n, pos[top[v]], pos[v]) + path_sum(u, fa[top[v]]);
}
if(dep[u] > dep[v]) swap(u, v);
return seg_sum(1, 1, n, pos[u], pos[v]);
}
int path_max(int u, int v){
if(top[u] != top[v]){
if(dep[top[u]] > dep[top[v]]) swap(u, v);
return max(seg_max(1, 1, n, pos[top[v]], pos[v]), path_max(u, fa[top[v]]));
}
if(dep[u] > dep[v]) swap(u, v);
return seg_max(1, 1, n, pos[u], pos[v]);
}
int main(){
n = read();
for(int i = 1; i < n; i++)
add(read(), read());
for(int i = 1; i <= n; i++)
val[i] = read();
init();
Q = read();
int u, v;
char op;
while(Q--){
while((op = getchar()) != 'C' && op != 'Q');
if(op == 'Q') op = getchar();
u = read(), v = read();
if(op == 'C') seg_change(1, 1, n, pos[u], v);
else if(op == 'S') printf("%d\n", path_sum(u, v));
else if(op == 'M') printf("%d\n", path_max(u, v));
}
return 0;
}
有一个编号为 1~n 的序列,提供 m 个操作,每个操作是一个序列a,表示将当前序列中的a[i]个数放在第i个位置上。从头至尾循环执行这m个操作,直到共执行了k个操作。求最后得到的序列。
~ 矩阵乘法。
0 0 1 0 1 3
1 0 0 0 * 2 = 1
0 0 0 1 3 4
0 1 0 0 4 2
首先把m个操作乘成一个矩阵,然后用快速幂求出它的 k/m 次方,再乘上剩下的前 k%m 个矩阵,得到的就是总的操作,最后再乘上 1~n 竖排排列的一个原始矩阵,得到的就是最终结果。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 105;
int n, m, k;
struct matrix {
int g[MAXN][MAXN];
matrix(){
memset(g, 0, sizeof(g));
}
matrix operator * (matrix b) {
matrix c;
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
c.g[i][j] += g[i][k] * b.g[k][j];
return c;
}
} one, op, tp, rest, ans;
void init(){
for(int i = 1; i <= n; i++)
one.g[i][i] = 1;
op = rest = one;
for(int i = 1; i <= n; i++)
ans.g[i][1] = i;
}
matrix qpow(matrix a, int x){
if(x == 0) return one;
matrix t = qpow(a, x >> 1);
if(x & 1) return t * t * a;
return t * t;
}
int main(){
scanf("%d%d%d", &n, &m, &k);
init();
for(int i = 1; i <= m; i++){
memset(tp.g, 0, sizeof(tp.g));
int v;
for(int j = 1; j <= n; j++){
scanf("%d", &v);
tp.g[j][v] = 1;
}
op = tp * op;
if(i <= k % m)
rest = tp * rest;
}
op = qpow(op, k / m);
ans = rest * op * ans;
for(int i = 1; i <= n; i++){
if(i > 1) printf(" ");
printf("%d", ans.g[i][1]);
}
printf("\n");
return 0;
}
@ 矩阵乘法注意顺序。
有一排格子,一开始在第 0 个格子,每次可以往右走 1~k 步,最后都要到达第 n 个格子。问有多少种方案?
~ 类似斐波那契!
首先我们知道状态转移方程:
构造一个这样的矩阵:
0 1 0 0 0 dp[i] dp[i+1]
0 0 1 0 0 dp[i+1] dp[i+2]
0 0 0 1 0 * dp[i+2] = dp[i+3]
0 0 0 0 1 dp[i+3] dp[i+4]
1 1 1 1 1 dp[i+4] dp[i+5]
求出左边那个矩阵的 m 次方就好了!
事实上,许多递推式都可以写成矩阵乘法,例如:
……的矩阵可以写成:
0 1 0 0
0 0 1 0
0 0 0 1
1 3 5 -2
这些矩阵共同特点是右上角 (n-1)*(n-1) 的小矩阵的主对角线为 1,最后一行为递推式中对应的系数。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int MO = 7777777, MAXN = 12;
int n, m; //n是技能等级, m是监狱个数
struct matrix {
ll g[MAXN][MAXN];
matrix(){
memset(g, 0, sizeof(g));
}
matrix operator * (matrix b) {
matrix c;
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % MO) % MO;
return c;
}
} op, ans, one;
matrix qpow(matrix a, int x){
if(x == 0) return one;
matrix t = qpow(a, x >> 1);
if(x & 1) return t * t * a;
return t * t;
}
void init(){
for(int i = 1; i <= n; i++)
one.g[i][i] = 1;
for(int i = 1; i < n; i++)
op.g[i][i + 1] = 1;
for(int i = 1; i <= n; i++)
op.g[n][i] = 1;
ans.g[n][1] = 1;
}
int main(){
scanf("%d%d", &n, &m);
init();
op = qpow(op, m);
ans = op * ans;
printf("%lld\n", ans.g[n][1]);
return 0;
}
水题:有向图,求两点之间恰好走k条边能到达的路径数目。
~
一个结论:k条边路径数目 = 邻接矩阵的k次方中对应的路径数目
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 55;
int n, s, f, m, p;
struct matrix {
ll g[MAXN][MAXN];
matrix(){
memset(g, 0, sizeof(g));
}
matrix operator * (matrix b) {
matrix c;
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;
return c;
}
} one, ans;
void init(){
for(int i = 1; i <= n; i++)
one.g[i][i] = 1;
}
matrix qpow(matrix a, int x){
matrix ans = one;
while(x){
if(x & 1) ans = ans * a;
a = a * a;
x >>= 1;
}
return ans;
}
int main(){
scanf("%d", &n);
init();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf("%lld", &ans.g[i][j]);
scanf("%d%d%d%d", &m, &s, &f, &p);
ans = qpow(ans, m);
printf("%lld\n", ans.g[s][f]);
return 0;
}
一个长 高 棋盘 (),用 的多米诺骨牌完全覆盖,问有多少种方案。
~
用二进制表示某一列每个方格被覆盖与否的情况,然后考虑用各种方式填满这一列时,下一列的情况是什么。注意:禁止在当前列竖放多米诺骨牌,因为会和这一列的另一种情况重复,但下一列可以竖放。
例如下图,中间那列是“当前列”,当前列以左已经全部填满,当前列参差不齐,当前列以右还没填。:
100 111 111
100 -> 111 or 111
110 110 111
110 110 111
将所有能转换的两种状态之间连上边(如上图 0011 可以转换为 1100 或1111),得到了一个有向图。
在这个有向图上从 1111 出发,到 1111 结束,走 n 步,方案数就是覆盖棋盘的方案数。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int n, m, p, M;
const int WIDTH = 34;
struct matrix {
ll g[WIDTH][WIDTH];
matrix(){
memset(g, 0, sizeof(g));
}
matrix(int x){
memset(g, 0, sizeof(g));
for(int i = 0; i < M; i++)
g[i][i] = 1;
}
matrix operator * (matrix b){
matrix c;
for(int k = 0; k < M; k++)
for(int i = 0; i < M; i++)
for(int j = 0; j < M; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;
return c;
}
} mp;
matrix qpow(matrix a, int x){
matrix ans(1);
while(x){
if(x & 1) ans = ans * a;
a = a * a;
x >>= 1;
}
return ans;
}
// Matrix67 的神奇位运算我找不到了 -_-|||
//自己编了一个十分naive的暴力判断,也能用
bool getbit(int num, int x){
return num & (1 << x);
}
void init(){
for(int i = 0; i < M; i++)
for(int j = 0; j < M; j++){
int ok = 1, rem = j;
for(int k = 0; k < m; k++){
if(!getbit(i, k)){//如果母串该位为0
if(!getbit(rem, k)) ok = 0;
//则子串该位必为1
else rem -= (1 << k);
//去掉这些横放造成的突起
}
}
//去掉横放突起后,剩下的都是竖放的
int cnt = 0;
for(int k = 0; k < m; k++){
if(getbit(rem, k)) cnt++;
//数数竖放挨在一起的有多少
if(!getbit(rem, k)){
if(cnt & 1) ok = 0;
cnt = 0;
}
}
if(cnt & 1) ok = 0;
if(ok) mp.g[i][j] = 1;
}
}
int main(){
scanf("%d%d%d", &n, &m, &p);
M = 1 << m ;
init();
mp = qpow(mp, n);
printf("%lld\n", mp.g[M - 1][M - 1]);
return 0;
}
有一棵n叉树,深度是无限的,每个结点有n个儿子。从左到右编号为1到n号儿子,第i号儿子离该结点的距离是di。现在要统计一下距离根结点不超过x的结点有多少个。
首先状态转移方程是显然的/*然鹅我一开始写错了*/
dp[i]表示到根节点距离不超过x的节点个数,cnt[j]输入数据中的表示长度为j的边的个数:
然后可以构造一个类似下面这样的矩阵:
1 0 0 0 0 1 1
0 0 1 0 0 dp[i-4] dp[i-3]
0 0 0 1 0 * dp[i-3] = dp[i-2]
0 0 0 0 1 dp[i-2] dp[i-1]
1 d c b a dp[i-1] dp[i]
其中 abcd 分别为 cnt[1], cnt[2], cnt[3], cnt[4]。
2~4行右侧3*3正方形是单位矩阵。
第一行第一列的1保证了所得结果的第一行永远是1。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 101, P = 1000000007;
int n, x;
struct matrix {
ll g[N+2][N+2];
matrix(){
memset(g, 0, sizeof(g));
}
matrix(int k){
memset(g, 0, sizeof(g));
for(int i = 0; i <= N; i++)
g[i][i] = 1;
}
matrix operator * (matrix b){
matrix c;
for(int k = 0; k <= N; k++)
for(int i = 0; i <= N; i++)
for(int j = 0; j <= N; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % P) % P;
return c;
}
} op, ori;
matrix qpow(matrix a, int x){
matrix ans(1);
while(x){
if(x & 1) ans = ans * a;
a = a * a;
x >>= 1;
}
return ans;
}
int main(){
scanf("%d%d", &n, &x);
for(int i = 1; i <= n; i++){
int t;
scanf("%d", &t);
op.g[N][N - t + 1]++;
}
op.g[0][0] = op.g[N][0] = 1;
for(int i = 1; i < N; i++)
op.g[i][i + 1] = 1;
ori.g[N][0] = ori.g[0][0] = 1;
op = qpow(op, x);
ori = op * ori;
printf("%lld\n", ori.g[N][0]);
return 0;
}
给出平面内一个圆与一个三角形,问它们是否相交(“相交”指边相交)。
呜呼噫嘻……
首先可以发现,三角形的三个顶点都在圆内时必然不相交,有内有外时必然相交。
三个顶点都在圆外时要求圆心到线段的距离。
怎么求?
设圆心为C,线段两端点为A和B,那么∠CAB和∠CBA都是锐角时,AB边上的高(即点到直线距离)在线段AB上,用点到直线距离求,否则取两端点圆心距离较小者。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const double eps = 0.000000001;
int T;
double xc, yc, r, xp[4], yp[4], k, b, dis;
//x_circle; x_point; kx + y + b = 0
double point_dis_square(double xa, double ya){
return (xa - xc)*(xa - xc) + (ya - yc)*(ya - yc);
}
bool calc_line(double xa, double ya, double xb, double yb){
//求一个形如 kx + y + b的解析式
if(abs(xa - xb) <= eps) return 0;//没有k
k = -(ya - yb)/(xa - xb);
b = (xb*ya - xa*yb)/(xa - xb);
return 1;//有k
}
double calc_dis(double xa, double ya, double xb, double yb){
double xe = xc - xa, ye = yc - ya, xf = xb - xa, yf = yb - ya, xg = xb - xc, yg = yb - yc;
// 求点乘判断钝角(后悔自己怎么没写结构体……)
if(xe*xf + ye*yf > 0 && xf*xg + yf*yg > 0){//如果∠CAB和∠CBA都为锐角
if(calc_line(xa, ya, xb, yb))//如果斜率存在,可求解析式
return abs(k*xc + yc + b) / sqrt(k*k + 1);
else
return abs(xa - xc);//斜率不存在,直接求x轴距离
}
return min(sqrt(point_dis_square(xa, ya)), sqrt(point_dis_square(xb, yb)));
//如果发现了钝角,则取线段两端点与原点距离中更小的。
}
int main(){
scanf("%d", &T);
while(T--){
scanf("%lf%lf%lf", &xc, &yc, &r);
for(int i = 1; i <= 3; i++)
scanf("%lf%lf", &xp[i], &yp[i]);
bool all_out = 1, all_in = 1;
for(int i = 1; i <= 3; i++){
if(abs(point_dis_square(xp[i], yp[i]) - r*r) <= eps)
all_out = all_in = 0;
else if(point_dis_square(xp[i], yp[i]) > r*r) all_in = 0;
else all_out = 0;
}
if(!all_out){//如果点不都在外面
if(all_in) printf("No\n");//都在里面
else printf("Yes\n");//有里有外
continue;
}
bool got_ans = 0;
for(int i = 1; i <= 3; i++){
dis = calc_dis(xp[i], yp[i], xp[i%3+1], yp[i%3+1]);
if(dis <= r){ //线段与圆相切或相交
printf("Yes\n");
got_ans = 1;
break;
}
}
if(!got_ans) printf("No\n");
}
return 0;
}
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
如2 4 3 1中,2 1,4 3,4 1,3 1是逆序,逆序数是4。1-n的全排列中,逆序数最小为0(正序),最大为n*(n-1) / 2(倒序)
给出2个数n和k,求1-n的全排列中,逆序数为k的排列有多少种?
例如:n = 4 k = 3。1 2 3 4的排列中逆序为3的共有6个,分别是:
1 4 3 2
2 3 4 1
2 4 1 3
3 1 4 2
3 2 1 4
4 1 2 3由于逆序排列的数量非常大,因此只需计算并输出该数 Mod 10^9 + 7的结果就可以了。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行2个数n,k。中间用空格分隔。(2 <= n <= 1000, 0 <= k <= 20000)
Output
共T行,对应逆序排列的数量 Mod (10^9 + 7)
Input示例
1
4 3
Output示例
6
~
dp[i][j] 表示 1~i 的全排列中逆序对为 j 的数目。
考虑第 i 个数插在原先的排列中,可以插在 0~(i-1) 号后面,分别新形成 0~(i-1) 个逆序对,于是:
但是这个复杂度炒鸡高,于是我们可以求前缀和,也可以再求出 dp[i][j-1]:
两式相减:
移项:
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int P = 1000000007, N = 1000, K = 20000;
int dp[N+3][K+3], T, n, k;
int main(){
dp[0][0] = 1;
for(int i = 1; i <= N; i++){
dp[i][0] = 1;
for(int j = 1; j < i; j++)
dp[i][j] = (dp[i][j-1] + dp[i-1][j]) % P;
for(int j = i; j <= K; j++)
dp[i][j] = ((dp[i][j-1] + dp[i-1][j] - dp[i-1][j-i]) % P + P) % P;
}
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &k);
printf("%d\n", dp[n][k]);
}
return 0;
}
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 50005;
int s[N], cnt, n, tmp;
int main(){
scanf("%d", &n);
s[0] = -0x3f3f3f3f;
for(int i = 1; i <= n; i++){
scanf("%d", &tmp);
if(tmp > s[cnt]) s[++cnt] = tmp;
else{
int pos = upper_bound(s + 1, s + cnt + 1, tmp) - s;
s[pos] = tmp;
}
}
printf("%d\n", cnt);
return 0;
}
给出一些数,求用这些数组成的最长等差序列的长度。
题解主要内容是:
dp[i][j] 表示以 i、j 号元素开头的最长等差数列长度。
自右向左枚举每个数 j, 在左、右分别找到 i 和 k 使 {i, j, k} 为等差数列,若找到,则 dp[i][j] = dp[j][k] + 1。
@ 虽然迫于空间限制必须开short,但是输入的那些数必须开int啊……
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 10003;
short f[N][N];
int n, ans = 2, a[N];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
f[i][j] = 2;
for(int j = n; j; j--){
int i = j - 1, k = j + 1;
while(i && k <= n){
if(a[i] + a[k] > 2 * a[j]) i--;
else if(a[i] + a[k] < 2 * a[j]) k++;
else {
f[i][j] = f[j][k] + 1;
if(f[i][j] > ans) ans = f[i][j];
i--, k++;
}
}
}
printf("%d\n", ans);
return 0;
}
给出一个长度为N的正整数数组,不改变数组元素的顺序,将这N个数分为K组。各组中元素的和分别为S1,S2....Sk。如何分组,使得S1至Sk中的最大值最小?
例如:1 2 3 4 5 6分为3组,{1 2 3} {4 5} {6},元素和为6, 9, 6,最大值为9。也可以分为{1 2 3 4} {5} {6}。元素和为:10 5 6,最大值为10。因此第一种方案更优。并且第一种方案的最大值是所有方案中最小的。输出这个最小的最大值。Input
第1行:2个数N, K,中间用空格分隔,N为数组的长度,K为要分为多少组。(2 <= K < N <= 50000)
第2 - N + 1行:数组元素(1 <= A[i] <= 10^9)
Output
输出这个最小的最大值。
其实特别水!
只是思维一直卡在DP里!
其实二分答案就好了!
唉!
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005;
typedef long long ll;
ll n, k, a[N], l, r, mid;
bool check(){
ll sum = 0, cnt = 1;
for(int i = 1; i <= n; i++){
sum += a[i];
if(sum > mid)
sum = a[i], cnt++;
if(cnt > k)
return 0;
}
return 1;
}
int main(){
scanf("%lld%lld", &n, &k);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), r += a[i];
while(l < r){
mid = (l + r) >> 1;
if(check()) r = mid;
else l = mid + 1;
}
printf("%lld\n", l);
return 0;
}
给出三个N*N的矩阵A, B, C,问A * B是否等于C?
正解居然是随机乱搞……
随机“一条” 1*N 的矩阵 D,分别计算 C*D 和 A*B*D (就等于 A*(B*D)),判断结果是否相同。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <cctype>
using namespace std;
int sread(){
char c;
int sign = 1, ans = 0;
while(!isdigit(c = getchar()))
if(c == '-') sign = -1;
ans = c - '0';
while(isdigit(c = getchar()))
ans = ans * 10 + c - '0';
return ans * sign;
}
const int N = 501;
int n, A[N][N], B[N][N], C[N][N], tp[N], ans1[N], ans2[N], ans3[N];
void gener(){
for(int i = 1; i <= n; i++)
tp[i] = rand() % 233;
memset(ans1, 0, sizeof(ans1));
memset(ans2, 0, sizeof(ans2));
memset(ans3, 0, sizeof(ans3));
}
int main(){
srand((int)time(0));
n = sread();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
A[i][j] = sread();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
B[i][j] = sread();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
C[i][j] = sread();
for(int CNT = 1; CNT <= 2; CNT++){
gener();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
ans1[i] += B[i][j] * tp[j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
ans2[i] += A[i][j] * ans1[j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
ans3[i] += C[i][j] * tp[j];
for(int i = 1; i <= n; i++)
if(ans3[i] != ans2[i]){
printf("No\n");
return 0;
}
}
printf("Yes\n");
return 0;
}
被标题吸引进来……却发现跟Chrome的恐龙毫无关系……
相信网络不好的选手一定很熟悉Chrome里面那个恐龙的游戏,这个题目就是根据那个游戏简化得来的。
给出一个正整数n,把恐龙的跳跃简化成一个[0,n)的随机数,再给出一个正整数m,把障碍简化为[0,n)中m个不同的的整数,把分数简化成所有生成的随机数的和。
把整个游戏简化为,每次生成一个[0,n)的随机数,如果这个随机数和给出的m个数字中的其中一个数字相等,那么就停止生成随机数,否则继续生成,求出所有生成的数的和的期望。
在某个小破地方我曾经推过这样一个式子:
的解是
所以这道题的解就是 。
#include <cstdio>
long long n, m;
int main(){
scanf("%lld%lld", &n, &m);
printf("%.6lf", (double) n*(n-1)/2/m);
return 0;
}
随手记一下(三)
带权中位数?
给出一个n,求1-n这n个数,同n的最大公约数的和。比如:n = 6
1,2,3,4,5,6 同6的最大公约数分别为1,2,3,2,1,6,加在一起 = 15
唉,数论……又不会。
题解:
最大公约数的种数不多,所以我们采取枚举最大公约数的策略。
最大公约数一定是n的因数,所以枚举每个n的约数x,要求 1~n 中所有满足 gcd(y, n) == x 的y的个数,等价于 gcd(y/x, n/x) == 0 的y的个数,所以只要求 phi(n/x)。
#include <cstdio>
using namespace std;
typedef long long ll;
int n;
ll ans;
int euler(int x){
int res = x;
for(int i = 2; i * i <= x; i++){
if(x % i == 0) res = res / i * (i - 1);
while(x % i == 0) x /= i;
}
if(x > 1) res = res / x * (x - 1);
return res;
}
int main(){
scanf("%d", &n);
for(int i = 1; i * i <= n; i++)
if(n % i == 0){
ans += i * euler(n / i);
if(i * i < n)
ans += n / i * euler(i);
}
printf("%lld\n", ans);
return 0;
}
给出一个数组A,经过一次处理,生成一个数组S,数组S中的每个值相当于数组A的累加,比如:A = {1 3 5 6} => S = {1 4 9 15}。如果对生成的数组S再进行一次累加操作,{1 4 9 15} => {1 5 14 29},现在给出数组A,问进行K次操作后的结果。(每次累加后的结果 mod 10^9 + 7)
Input
第1行,2个数N和K,中间用空格分隔,N表示数组的长度,K表示处理的次数(2 <= n <= 5000, 0 <= k <= 10^9, 0 <= a[i] <= 10^9)Output
共N行,每行一个数,对应经过K次处理后的结果。每次累加后mod 10^9 + 7。
看到这种操作,首先想到的就是矩阵乘法。
然而一个严峻的问题是,n <= 5000...会超时的。
但是由于这个矩阵(主对角线及其左下方都为1,其余为0)长得非常规律,我们可以试着找到它的m次方的规律。
最后发现是个组合数,可以快速求出。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const ll P = 1000000007;
const int N = 5005;
ll n, k, a[N], fake_matrix[N], inv[N];
ll qpow(ll a, ll x){
ll ans = 1;
while(x){
if(x & 1) ans = ans * a % P;
a = a * a % P;
x >>= 1;
}
return ans;
}
int main(){
scanf("%lld%lld", &n, &k);
inv[0] = fake_matrix[1] = 1;
for(int i = 1; i <= n; i++)
inv[i] = inv[i - 1] * qpow(i, P - 2) % P;
k--;
ll last = 1;
for(int i = 1; i <= n; i++){
last = last * (k + i) % P;
fake_matrix[i + 1] = inv[i] * last % P;
}
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
ll ans = 0;
for(int i = 1; i <= n; i++){
ans = 0;
for(int j = 1; j <= i; j++)
ans = (ans + fake_matrix[i - j + 1] * a[j] % P) % P;
printf("%lld\n", ans);
}
return 0;
}
N * N的方格,从左上到右下画一条线。一个机器人从左上走到右下,只能向右或向下走。并要求只能在这条线的上面或下面走,不能穿越这条线,有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 10007的结果。
Input
输入一个数N(2 <= N <= 10^9)。
Output
输出走法的数量 Mod 10007。
这个方案数就是卡特兰数………………
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P = 10007;
ll n, fac[P], inv[P];
ll qpow(ll a, ll x){
ll res = 1;
while(x){
if(x & 1) res = res * a % P;
a = a * a % P;
x >>= 1;
}
return res;
}
void init(){
fac[0] = inv[0] = 1;
for(int i = 1; i < P; i++){
fac[i] = fac[i-1] * i % P;
inv[i] = qpow(fac[i], P - 2);
}
}
ll rawC(ll x, ll y){
return fac[x] * inv[y] * inv[x - y];
}
ll C(ll x, ll y){
ll res = 1;
while(x || y){
res = res * rawC(x % P, y % P) % P;
x /= P, y /= P;
}
return res;
}
int main(){
scanf("%lld", &n);
n--;
init();
printf("%lld\n", C(2 * n, n) * qpow(n + 1, P - 2) % P * 2 % P);
return 0;
}
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 10005, M = 1000005, INF = 2147483647;
int n, m, s, cnt, pos[N], dis[N];
struct node {
int dis, id; //id: 堆上节点在图上的编号
bool operator < (node b){
return dis < b.dis;
}
} hp[N];
void node_swap(node &a, node &b){
swap(pos[a.id], pos[b.id]);
node c;
c = a, a = b, b = c;
}
void checkup(int x){
while(x > 1 && hp[x] < hp[x >> 1]){
node_swap(hp[x], hp[x >> 1]);
x >>= 1;
}
}
void checkdown(int x){
while((x << 1) <= cnt){
int y = hp[x << 1 | 1] < hp[x << 1] ? (x << 1 | 1) : (x << 1);
if(y <= cnt && hp[y] < hp[x]) node_swap(hp[x], hp[y]), x = y;
else break;
}
}
void push(int id, int x){
hp[++cnt].id = id, hp[cnt].dis = x;
dis[id] = x;
pos[id] = cnt;
checkup(cnt);
}
void pop(){
hp[1] = hp[cnt--];
checkdown(1);
}
void decrease(int id, int x){
if(x >= dis[id]) return;
hp[pos[id]].dis = x;
dis[id] = x;
checkup(pos[id]);
}
node top(){ return hp[1]; }
bool empty(){ return cnt == 0;}
int ecnt, adj[N], go[M], w[M], nxt[M];
bool vis[N];
void add(int u, int v, int ww){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
w[ecnt] = ww;
}
int main(){
scanf("%d%d%d", &n, &m, &s);
for(int i = 1; i <= m; i++){
int u, v, ww;
scanf("%d%d%d", &u, &v, &ww);
add(u, v, ww);
}
push(s, 0);
for(int i = 1; i <= n; i++) if(i != s) push(i, INF);
while(!empty()){
node u = top();
pop();
vis[u.id] = 1;
for(int e = adj[u.id]; e; e = nxt[e])
if(!vis[go[e]] && u.dis < INF)
decrease(go[e], w[e] + u.dis);
}
for(int i = 1; i <= n; i++)
printf("%d ", dis[i]);
return 0;
}
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
void mysort(int *a, int l, int r){
if(l >= r) return;
int i = l, j = r, k = l + rand() % (r - l + 1);
while(i < j){
while(i < k && a[i] <= a[k]) i++;
swap(a[i], a[k]), k = i;
while(j > k && a[j] >= a[k]) j--;
swap(a[j], a[k]), k = j;
}
mysort(a, l, k - 1);
mysort(a, k + 1, r);
}
int main(){
srand((int)time(0));
int n, a[100005];
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
mysort(a, 1, n);
for(int i = 1; i <= n; i++)
printf("%d ", a[i]);
puts("");
return 0;
}
随机数初始化:srand((int)time(0))
随机数:rand()
随手记一下(四)
用N个不同的字符(编号1 - N),组成一个字符串,有如下要求:
(1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符。如果不作为结尾字符而是中间的字符,则该字符后面可以接任意字符。
(2) 对于编号为i的字符,如果2 * i <= n,则该字符不可以作为结尾字符。作为中间字符,那么后面接的字符编号一定要 >= 2 * i。
问有多少长度为M且符合条件的字符串,由于数据很大,只需要输出该数Mod 10^9 + 7的结果。
例如:N = 2,M = 3。则abb, bab, bbb是符合条件的字符串,剩下的均为不符合条件的字符串。Input
输入2个数,N, M中间用空格分割,N为不同字符的数量,M为字符串的长度。(2 <= N, M <= 10^6)
Output
输出符合条件的字符串的数量。由于数据很大,只需要输出该数Mod 10^9 + 7的结果。
可以把一个合法字符串划分成若干个合法的“链”,每条链的结尾都是一个编号大于n/2的字符。设f[i]为长度为i的合法字符串数量,g[i]为长度为i的链的数量,则:
由于“链”的长度不超过,处理f[i]需要。
那么怎么求g[i]呢?我们可以设一个h[i][j]表示长度为i,以j开头的链的个数。
可以一边求h[i][j]一边记录g[i]。h[i][j]可以滚动数组压缩成h[j]。处理h[j]需要。
总复杂度。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f, P = 1000000007;
const int N = 1000005, L = 23;
int n, m, l;
ll f[N], h[N], g[L], sum[N];
//f[i]: 长度为i的字符串个数;
//h([i])[j]:长度为i以j开头的链数;
//g[i]: 长度为i的链数
//sum[j]: 上一层的h的后缀和
int main(){
scanf("%d%d", &n, &m);
l = ceil(log2((double)n)) + 1;
/******预处理******/
f[0] = g[0] = g[1] = sum[n] = 1;
for(int i = n - 1; i * 2 > n; i--)
sum[i] = sum[i + 1] + 1, g[1]++;
//i*2>n的i可以作为“链”的结尾
for(int i = n/2; i; i--)
sum[i] = sum[i + 1];
//其他不可以作为“链”的结尾
/******处理g数组******/
for(int i = 2; i <= l; i++){
for(int j = n / 2; j; j--)
h[j] = sum[2 * j];//上一层[2*j, n]相加
sum[n] = 0;
for(int j = n - 1; j; j--)
sum[j] = (sum[j + 1] + h[j]) % P;//这一层的sum
g[i] = sum[1];
}
/******处理f数组******/
for(int i = 1; i <= m; i++){
for(int j = 1; j <= min(i, l); j++)
f[i] = (f[i] + f[i - j] * g[j]) % P;
}
printf("%lld\n", f[m]);
return 0;
}
把一个数的约数个数定义为该数的复杂程度,给出一个n,求1-n中复杂程度最高的那个数。
例如:12的约数为:1 2 3 4 6 12,共6个数,所以12的复杂程度是6。如果有多个数复杂度相等,输出最小的。Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 100)
第2 - T + 1行:T个数,表示需要计算的n。(1 <= n <= 10^18)
Output
共T行,每行2个数用空格分开,第1个数是答案,第2个数是约数的数量。
正解是搜索……
优化:每一个素数的指数不能大于上一个素数的指数,否则这样生成的数一定更大
@ 注意:判断是否超出n的范围时,一定要用 nowans <= n / plst[x]
,而不是乘好了再判断,否则会越界。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 60;
int T, plst[N + 5], pcnt;
ll n, val, ans;
bool np[N + 5];
void euler(){
np[0] = np[1] = 1;
for(int i = 2; i <= N; i++){
if(!np[i]) plst[++pcnt] = i;
for(int j = 1; j <= pcnt && plst[j] * i <= N; j++){
np[plst[j] * i] = 1;
if(i % plst[j] == 0)
break;
}
}
}
void dfs(int x, int lastnum, ll nowval, ll nowans){
if(val < nowval || (val == nowval && ans > nowans)){
val = nowval;
ans = nowans;
}
if(x > pcnt) return;
for(int i = 1; i <= lastnum && nowans <= n / plst[x]; i++){
nowans *= plst[x];
dfs(x + 1, i, nowval * (i + 1), nowans);
}
}
int main(){
euler();
scanf("%d", &T);
while(T--){
scanf("%lld", &n);
val = ans = 0;
dfs(1, 70, 1, 1);
printf("%lld %lld\n", ans, val);
}
return 0;
}
N个整数组成的数组,定义子数组a[i]..a[j]的宽度为:max(a[i]..a[j]) - min(a[i]..a[j]),求所有子数组的宽度和。
上次在某vijos蒟蒻比赛里就看见过这道题,没想出来……
首先,求所有子数组的宽度和就是所有子数组的最大值之和减去所有子数组的最小值之和。那么我们就要计算每个元素对多少个子数组有贡献。
我的做法是暴力的ST表(@ 记得强制转换 long long!):
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005;
int n, a[N], mi[N][20], ma[N][20];
ll sum_min, sum_max;
void init(){
for(int i = 1; i <= n; i++)
mi[i][0] = ma[i][0] = i;
for(int j = 1; (1 << j) <= n; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
mi[i][j] = a[mi[i][j - 1]] > a[mi[i + (1 << (j - 1))][j - 1]] ? mi[i + (1 << (j - 1))][j - 1] : mi[i][j - 1];
ma[i][j] = a[ma[i][j - 1]] < a[ma[i + (1 << (j - 1))][j - 1]] ? ma[i + (1 << (j - 1))][j - 1] : ma[i][j - 1];
}
}
int getmin(int l, int r){
int j = log2((double) r - l + 1);
return a[mi[l][j]] > a[mi[r - (1 << j) + 1][j]] ? mi[r - (1 << j) + 1][j] : mi[l][j];
}
int getmax(int l, int r){
int j = log2((double) r - l + 1);
return a[ma[l][j]] < a[ma[r - (1 << j) + 1][j]] ? ma[r - (1 << j) + 1][j] : ma[l][j];
}
int max_lim_left(int k, int l, int r){
if(l == r) return l;
int mid = (l + r) >> 1;
if(getmax(mid, k) != k) return max_lim_left(k, mid + 1, r);
return max_lim_left(k, l, mid);
}
int max_lim_right(int k, int l, int r){
if(l == r) return l;
int mid = (l + r + 1) >> 1;
if(getmax(k, mid) != k) return max_lim_right(k, l, mid - 1);
return max_lim_right(k, mid, r);
}
int min_lim_left(int k, int l, int r){
if(l == r) return l;
int mid = (l + r) >> 1;
if(getmin(mid, k) != k) return min_lim_left(k, mid + 1, r);
return min_lim_left(k, l, mid);
}
int min_lim_right(int k, int l, int r){
if(l == r) return l;
int mid = (l + r + 1) >> 1;
if(getmin(k, mid) != k) return min_lim_right(k, l, mid - 1);
return min_lim_right(k, mid, r);
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
init();
for(int i = 1; i <= n; i++){
int l = max_lim_left(i, 1, i), r = max_lim_right(i, i, n);
sum_max += (ll)(i - l + 1) * (r - i + 1) * a[i];
l = min_lim_left(i, 1, i), r = min_lim_right(i, i, n);
sum_min += (ll)(i - l + 1) * (r - i + 1) * a[i];
}
printf("%lld\n", sum_max - sum_min);
return 0;
}
然鹅网上有更妙的办法(单调栈)
TODO
问 能否分解成 的形式
如果可以 输出 m%(1e9+7) 否则 输出no
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const ll m = 1000000007;
ll n;
struct matrix {
ll g[2][2];
matrix(){
memset(g, 0, sizeof(g));
}
matrix(int x){
g[0][1] = g[1][0] = 0, g[0][0] = g[1][1] = 1;
}
matrix operator * (matrix b){
matrix c;
for(int k = 0; k < 2; k++)
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % m) % m;
return c;
}
} op;
matrix ksm(matrix a, ll x){
matrix ret(1);
while(x){
if(x & 1) ret = ret * a;
a = a * a;
x >>= 1;
}
return ret;
}
int main(){
op.g[0][0] = op.g[1][0] = op.g[1][1] = 1;
op.g[0][1] = 2;
scanf("%lld", &n);
if(n == 1){
printf("2\n");
return 0;
}
op = ksm(op, n - 1);
if(n & 1)
printf("%lld\n", 2 * (op.g[1][0] + op.g[1][1]) % m * (op.g[1][0] + op.g[1][1]) % m);
else
printf("%lld\n", (op.g[0][0] + op.g[0][1]) % m * (op.g[0][0] + op.g[0][1]) % m);
return 0;
}
一个无向图,可能有自环,有重边,每条边有一个边权。你可以从任何点出发,任何点结束,可以经过同一个点任意次。但是不能经过同一条边2次,并且你走过的路必须满足所有边的权值严格单调递增,求最长能经过多少条边。
Input
第1行:2个数N, M,N为节点的数量,M为边的数量(1 <= N <= 50000, 0 <= M <= 50000)。节点编号为0 至 N - 1。
第2 - M + 1行:每行3个数S, E, W,表示从顶点S到顶点E,有一条权值为W的边(0 <= S, E <= N - 1, 0 <= W <= 10^9)。
Output
输出最长路径的长度。
把边排序后DP即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 50005, M = 100005;
int n, m;
int ecnt = 1, go[M], nxt[M], w[M], adj[N];
int id[M], dp[M], ans;
void add(int u, int v, int ww){
go[++ecnt] = v;
w[ecnt] = ww;
id[ecnt] = ecnt;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
bool cmp(int a, int b){
return w[a] < w[b];
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1, u, v, ww; i <= m; i++)
scanf("%d%d%d", &u, &v, &ww), add(u, v, ww), add(v, u, ww);
sort(id + 2, id + ecnt + 1, cmp);
for(int i = 2; i <= ecnt; i++){
int x = id[i], u = go[x ^ 1];//u是x的起点
dp[x] = 1;
for(int e = adj[u]; e; e = nxt[e]){//枚举起点出发的每条边
if(x != e && w[e] < w[x])
dp[x] = max(dp[x], dp[e ^ 1] + 1);
}
ans = max(ans, dp[x]);
}
printf("%d\n", ans);
return 0;
}
一个字符串的前缀是指包含该字符第一个字母的连续子串,例如:abcd的所有前缀为a, ab, abc, abcd。
给出一个字符串S,求其所有前缀中,字符长度与出现次数的乘积的最大值。
例如:S = "abababa" 所有的前缀如下:"a", 长度与出现次数的乘积 1 * 4 = 4,
"ab",长度与出现次数的乘积 2 * 3 = 6,
"aba", 长度与出现次数的乘积 3 * 3 = 9,
"abab", 长度与出现次数的乘积 4 * 2 = 8,
"ababa", 长度与出现次数的乘积 5 * 2 = 10,
"ababab", 长度与出现次数的乘积 6 * 1 = 6,
"abababa", 长度与出现次数的乘积 7 * 1 = 7.其中"ababa"出现了2次,二者的乘积为10,是所有前缀中最大的。
Input
输入字符串S, (1 <= L <= 100000, L为字符串的长度),S中的所有字符均为小写英文字母。
Output
输出所有前缀中字符长度与出现次数的乘积的最大值。
用KMP计算出每一位的nxt,然后从后往前,cnt[nxt[i]] += nxt[i]。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
char s[N];
int n, nxt[N], cnt[N];
ll ans;
int main(){
scanf("%s", s + 1);
n = strlen(s + 1);
for(int i = 2, j = 0; i <= n; i++){
while(j && s[i] != s[j + 1]) j = nxt[j];
if(s[i] == s[j + 1]) j++;
nxt[i] = j;
}
for(int i = n; i; i--){
cnt[i]++;
cnt[nxt[i]] += cnt[i];
ans = max(ans, (ll) i * cnt[i]);
}
printf("%lld\n", ans);
return 0;
}
计算整数集合{1,2,3,4, .... N }满足下列条件的的排列个数:
在位置a1, a2, ..., aK小于其邻居(编号从0开始)。
在位置b1, b2, ..., bL大于其邻居。输出符合条件的排列数量Mod 1000000007的结果。例如:N = 4,a = {1}, b = {2},符合条件的排列为:
2 1 4 3
3 2 4 1
4 2 3 1
3 1 4 2
4 1 3 2Input
第1行:3个数N, K, L,分别表示数组的长度,限制a的长度,限制b的长度(1 <= N <= 5000, 1 <= K, L <= N)。
第2 - K + 1行:每行一个数,对应限制a的位置(1 <= ai <= N - 2)
第K + 2 - K + L + 1行:每行一个数,对应限制b的位置(1 <= bi <= N - 2)
Output
输出符合条件的排列数量Mod 1000000007的结果。
其实还是很简单的,但之前“+1”那块一直没弄懂。
用 dp[i][j] 表示前i个元素组成的、第i位为j的序列个数(防元素重复)。用数组s[i]记录第i位是否有特殊要求:s[i] = 0 则无要求,s[i] = 1 表示第i个元素大于第i-1个,s[i] = 2 表示第i个元素小于第i-1个。
前两种情况的状态转移都好说,分别是:
第三种略难,因为涉及到把一个新元素放在一个连续数字组成的序列后面……大概是这样的:
原序列:4 2 3 5 1
现在要求dp[6][3],即由1~6组成的,第6位为3的序列个数
求的就是形如 5 2 4 6 1 3 的序列个数
对比上下两个序列,下面序列就是上面序列中所有大于等于3的元素都加一
两种序列是一一对应的
所以对于第三种情况:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 5005, P = 1000000007;
int n, a, b;
int s[N], sum[N], dp[N];
int main(){
scanf("%d%d%d", &n, &a, &b);
for(int i = 1, tmp; i <= a; i++)
scanf("%d", &tmp), s[tmp + 1] = 2, s[tmp + 2] = 1;
for(int i = 1, tmp; i <= b; i++)
scanf("%d", &tmp), s[tmp + 1] = 1, s[tmp + 2] = 2;
dp[1] = sum[1] = 1;
for(int i = 2; i <= n; i++){
for(int j = 1; j <= n; j++){
if(s[i] == 0) dp[j] = sum[i - 1];
else if(s[i] == 1) dp[j] = sum[j - 1];
else dp[j] = ((sum[i - 1] - sum[j - 1]) % P + P) % P;
}
for(int j = 1; j <= n; j++)
sum[j] = (sum[j - 1] + dp[j]) % P;
}
printf("%d\n", sum[n]);
return 0;
}
我也想出题的时候出出这么妙的树形DP……
有两颗无根树Tree1与Tree2,这两棵树各有N个节点(2<=N<=4000)。每棵树中,N个节点都被标为0,1,2,..,N-1,这N个序号,不保证两颗树是同构的。
定义一个关于这两棵树的函数S(e1,e2),其中e1为Tree1的一条边,e2为Tree2的一条边,定义如下:
(1)如果将边e1从Tree1中移除(即Tree1在e1处断开),那么Tree1将分裂为两棵小树A1与B1;
(2)如果将边e2从Tree2中移除(即Tree2在e2处断开),那么Tree2将分裂为两棵小树A2与B2;
(3)令Set(Tree)表示Tree中所有节点的序号构成的集合,且令Fun(S1,S2)为集合S1与S2交集的大小(交集元素个数,空集时为0),即|S1 ∩ S2|,例如Fun({1,2,3,4},{7,3,2,8,6}) = 2;
(4)S(e1,e2) = max{ Fun(Set(A1),Set(A2)) , Fun(Set(A1),Set(B2)) , Fun(Set(B1),Set(A2)) ,
Fun(Set(B1),Set(B2)) }.其中 max{...} 为集合{...}中元素的最大值。
简单说,S(e1,e2)为Fun(X,Y)能取到的最大值,其中X=A1或B1,Y=A2或B2.
在这个问题中,你需要求对于所以边对(e1,e2),共计(N-1)*(N-1)对情况下,S(e1,e2)平方的和。即求如下公式的值。
一开始看着这一大堆东西毫无头绪,后来看讨论区两位大神点拨,豁然开朗……
两棵树各被分成两部分(A1、B1、A2、B2),对于任何一个编号,在(A1、B1)中要么属于A1要么属于B1,在(A2、B2)中要么属于A2要么属于B2。那么可以列个表:
A1 | B1 | |
---|---|---|
A2 | a | b |
B2 | c | d |
a、b、c、d为交集的大小,则a + b = c + d = a + c = b + d = n, 这样求出abcd中的任意一个,其余三个皆可求。
那么我们可以在一棵树中枚举切断的边,然后随便选择它的一侧,作为A1,记录一下每个编号是否属于A1; 之后在另一棵树上进行DP,令一个点及其子树作为A2,求出A1、A2的交集大小,然后就能求出其余三个交集大小,取max即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 4005;
int n;
int ecnt[2] = {1, 1}, go[2][2 * N], nxt[2][2 * N], adj[2][N];
int sze[N], rsze[N], ccnt;
bool col[N];
ll ans;
void add(int u, int v, bool p){
go[p][++ecnt[p]] = v;
nxt[p][ecnt[p]] = adj[p][u];
adj[p][u] = ecnt[p];
}
void color(int u, int pre){
int v;
//printf("%d <- %d\n", u, pre);
col[u] = 1, ccnt++;
for(int e = adj[0][u]; e; e = nxt[0][e])
if((v = go[0][e]) != pre)
color(v, u);
}
void dfs(int u, int pre){
int v;
sze[u] = col[u];
rsze[u] = 1;
for(int e = adj[1][u]; e; e = nxt[1][e])
if((v = go[1][e]) != pre){
dfs(v, u);
sze[u] += sze[v];
rsze[u] += rsze[v];
}
if(pre){
int res = max(sze[u], rsze[u] - sze[u]);
res = max(res, ccnt - sze[u]);
res = max(res, n - ccnt - rsze[u] + sze[u]);
ans += (ll) res * res;
}
}
int main(){
scanf("%d", &n);
for(int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u + 1, v + 1, 0), add(v + 1, u + 1, 0);
for(int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u + 1, v + 1, 1), add(v + 1, u + 1, 1);
for(int i = 2; i <= ecnt[0]; i += 2){
memset(col, 0, sizeof(col));
ccnt = 0;
color(go[0][i], go[0][i ^ 1]);
//printf("tree1 cut %d %d, ccnt = %d\n", go[0][i], go[0][i ^ 1], ccnt);
dfs(1, 0);
}
printf("%lld\n", ans);
return 0;
}
有一个三维的雕像,这个雕像被一个N*N*N的立方网格包围。巧合的是该雕像恰好能被网格划分,即这个N^3的立方网格中,每一个1*1*1的小立方块要么恰好完全是雕塑的一部分(该立方网格与雕塑的交就是该立方大小),要么这个立方块是空的(与雕塑的交为空)。这个雕塑是一个整体,其每个单位立方块的拓扑结构是相互连通的。两个立方块A与B是连通的要么(1)A与B存在公共面,(2)A与C连通且C与B连通。不幸的是这个雕塑被不小心破坏了,现在需要还原它,但幸运的是我们拥有这个雕像的三视图投影XY,YZ与ZX。其中XY是一个二维数组,表示一个沿着Z轴方向的投影,XY[i][j]是XY平面上(i,j)坐标处网格的投影信息,如果XY[i][j] = ‘ Y’ 说明该处是有阴影的,即三维空间坐标(i,j,0),(i,j,1)...(i,j,z)...(i,j,N-1)这N个格子中至少有一个立方块是实心的;相反如果XY[i][j] = ‘ N’ 说明(i,j)处是透光的,即维空间坐标(i,j,0),(i,j,1)...(i,j,z)...(i,j,N-1)这N个格子全部是空心的。同理YZ[i][j]表达YZ平面信息,YZ[i][j]对应{(x,i,j)|x=0,1,..,N-1}处的网格;而ZX[i][j]同理,对应{(j,y,i)|y=0,1,...,N-1}这些网格。由于种种原因,复原者发现这个三视图信息似乎构造不出一个合理的三维雕塑,他们希望确定这个三视图是否有错误。现在给出XY,YZ,ZX的信息,你需要判断该三视图是否有可能是某个雕塑的三视图投影。
例如一个2x2x2的立方网格,其三视图如下:
XY: YZ: ZX:
YN YN YN
NN NN NN
该视图显然可以构造一个雕塑出来,即(0,0,0)处是实心,其他7个立方块都是空心。而如果其三视图如下:
XY: YZ: ZX:
YN YN YN
NY NY NY
是不能构造一个雕塑出来,因为(0,0,0)处与(1,1,1)处都是实心,而它们不是连通的。Input
多组测试数据,第一行一个整数T,表示测试数据个数,其中1<=T<=5。
之后有T组测试数据,每组数据第一行一个整数N,即立方网格大小,1<=N<=10.
之后有3*N行,每行N个字符,每个字符不是'Y'就是‘N’,且前N行代表XY[][],中间N行代表YZ[][],最后N行是ZX[][].
Output
每组数据一行输出,输出雕塑存在可能性,如果有至少一种构造方法输出"Possible",否则"Impossible"。(都不含引号)Input示例
2
2
YN
NN
YN
NN
YN
NN
2
YN
NY
YN
NY
YN
NY
Output示例
Possible
Impossible
一开始搞一个n*n*n的实心立方体,然后把每条透光的部分抠掉,之后检查这个立方体的每一个联通块能否把所有不透光的地方挡满。
注意:
(1) 啥都没有(全是"N")是可行的
(2) 完全不存在的三视图(如只有一面有"Y",其余两面都全是"N")是不可行的
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 15;
//up, down, left, right, front, back
int dx[] = {0, 0, -1, 1, 0, 0}, dy[] = {0, 0, 0, 0, 1, -1}, dz[] = {1, -1, 0, 0, 0, 0};
int T, n;
bool fil[N][N][N], vis[N][N][N];
char s1[N][N], s2[N][N], s3[N][N];
bool y1[N][N], y2[N][N], y3[N][N];
void dfs(int x, int y, int z){
vis[x][y][z] = 1;
y1[x][y] = y2[y][z] = y3[z][x] = 1;
for(int i = 0; i < 6; i++){
if(fil[x + dx[i]][y + dy[i]][z + dz[i]] && !vis[x + dx[i]][y + dy[i]][z + dz[i]])
dfs(x + dx[i], y + dy[i], z + dz[i]);
}
}
bool check(){
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++){
if(s1[i][j] == 'Y' && !y1[i][j]) return 0;
if(s2[i][j] == 'Y' && !y2[i][j]) return 0;
if(s3[i][j] == 'Y' && !y3[i][j]) return 0;
}
return 1;
}
int main(){
scanf("%d", &T);
while(T--){
scanf("%d", &n);
bool ept = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
fil[i][j][k] = 1, vis[i][j][k] = 0;
for(int i = 1; i <= n; i++){
scanf("%s", s1[i] + 1);
for(int j = 1; j <= n; j++)
if(s1[i][j] == 'N'){
for(int k = 1; k <= n; k++)
fil[i][j][k] = 0;
}
else ept = 0;
}
for(int j = 1; j <= n; j++){
scanf("%s", s2[j] + 1);
for(int k = 1; k <= n; k++)
if(s2[j][k] == 'N'){
for(int i = 1; i <= n; i++)
fil[i][j][k] = 0;
}
else ept = 0;
}
for(int k = 1; k <= n; k++){
scanf("%s", s3[k] + 1);
for(int i = 1; i <= n; i++)
if(s3[k][i] == 'N'){
for(int j = 1; j <= n; j++)
fil[i][j][k] = 0;
}
else ept = 0;
}
bool ok = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
if(fil[i][j][k] && !vis[i][j][k]){
memset(y1, 0, sizeof(y1));
memset(y2, 0, sizeof(y2));
memset(y3, 0, sizeof(y3));
dfs(i, j, k);
ok = ok || check();
}
if(ok || ept) printf("Possible\n");
else printf("Impossible\n");
}
return 0;
}
N个整数组成的循环序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续的子段和的最大值(循环序列是指n个数围成一个圈,因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列)。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N+1行:N个整数 (-10^9 <= S[i] <= 10^9)
Output
输出循环数组的最大子段和。
循环数组最大子段和有且只有两种可能:
后者相当于把这个循环数组中间最小的那段剪去。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005;
typedef long long ll;
int n;
ll a[N], sum, lastmin, minn, lastmax, maxn;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), sum += a[i];
for(int i = 1; i <= n; i++){
lastmin = min(lastmin + a[i], a[i]);
minn = min(lastmin, minn);
lastmax = max(lastmax + a[i], a[i]);
maxn = max(lastmax, maxn);
}
printf("%lld\n", max(maxn, sum - minn));
return 0;
}
@ 不开 long long 见那啥
一个长度为N的数组A,从A中选出若干个数,使得这些数的和是N的倍数。
例如:N = 8,数组A包括:2 5 6 3 18 7 11 19,可以选2 6,因为2 + 6 = 8,是8的倍数。
Input
第1行:1个数N,N为数组的长度,同时也是要求的倍数。(2 <= N <= 50000)
第2 - N + 1行:数组A的元素。(0 < A[i] <= 10^9)
Output
如果没有符合条件的组合,输出No Solution。
第1行:1个数S表示你所选择的数的数量。
第2 - S + 1行:每行1个数,对应你所选择的数。
对于每一位求前缀和(模n),一共求了n个;如果其中有一个为0,则输出这一位前面所有的数即可,否则前缀和模n一共有(n - 1)种可能,则根据抽屉原理必有两个前缀和相等,输出相等的两位之间的数即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005;
int n, cnt[N];
ll a[N], sum;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
cnt[i] = -1;
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
sum = (sum + a[i]) % n;
if(cnt[sum] == -1) cnt[sum] = i;
else {
printf("%d\n", i - cnt[sum]);
for(int j = cnt[sum] + 1; j <= i; j++)
printf("%lld\n", a[j]);
return 0;
}
}
return 0;
}
@ sum一开始用的是数组,然后写成了sum[i] = sum[i] + a[i]……
……为了这点问题还买了数据!我的盾啊!
N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的。当所给的整数均为负数时和为0。
例如:{-2,11,-4,13,-5,-2, 4}将 -4 和 4 交换,{-2,11,4,13,-5,-2, -4},最大子段和为11 + 4 + 13 = 28。Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出交换一次后的最大子段和。
O(nlogn):枚举要被替换的数,经预处理可以得到它向左右延伸的L、R,然后在[L, R]以外的部分找最大值来替换它即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005, INF = 0x3f3f3f3f;
int n, lp[N], rp[N];
ll a[N], ma[N][20], ln[N], rn[N], ans;
ll getma(int l, int r){
if(r < l) return -INF;
int j = log2((double) r - l + 1);
return max(ma[l][j], ma[r - (1 << j) + 1][j]);
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), ma[i][0] = a[i];
for(int i = 1; i <= n; i++)
if(ln[i - 1] + a[i] > a[i])
lp[i] = lp[i - 1], ln[i] = ln[i - 1] + a[i];
else lp[i] = i, ln[i] = a[i];
for(int i = n; i; i--)
if(rn[i + 1] + a[i] > a[i])
rp[i] = rp[i + 1], rn[i] = rn[i + 1] + a[i];
else rp[i] = i, rn[i] = a[i];
for(int j = 1; (1 << j) <= n; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
ma[i][j] = max(ma[i][j - 1], ma[i + (1 << (j - 1))][j - 1]);
for(int i = 1; i <= n; i++){
ll rpl = max(getma(1, lp[i] - 1), getma(rp[i] + 1, n));
rpl = max(rpl, a[i]);
ans = max(ans, ln[i] + rn[i] - 2 * a[i] + rpl);
}
printf("%lld\n", ans);
return 0;
}
N个整数组成的序列a[1],a[2],a[3],…,a[n],从中选出一个子序列(a[i],a[i+1],…a[j]),使这个子序列的和>0,并且这个和是所有和>0的子序列中最小的。
例如:4,-1,5,-2,-1,2,6,-2。-1,5,-2,-1,序列和为1,是最小的。
在之前所有前缀和里找当前前缀和的前驱。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <set>
using namespace std;
typedef long long ll;
const int N = 50005;
int n;
ll a[N], sum, ans = 0x3f3f3f3f3f3f3f3f;
set <ll> s;
int main(){
scanf("%d", &n);
s.insert(0);
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
sum += a[i];
set <ll> :: iterator p = s.lower_bound(sum);
if(p != s.begin())
--p, ans = min(ans, sum - *p);
s.insert(sum);
}
printf("%lld\n", ans);
return 0;
}
@ 一开始没把0扔进去导致无法处理答案包含第一个数的情况,错了前两个点。
来自豪神的推荐。
给出一个长为n的序列,有m个询问,每次询问区间[l, r]中所有出现偶数次的数的异或和。()
所有出现偶数次的数的异或和就是所有数的异或和^所有不同的数的异或和。(因为所有数的异或和就是所有出现奇数次的数的异或和,再异或上不同的数的异或和,就把“不同的数的异或和”中出现奇数次的数“去掉了”。)
问题转化为询问一个区间中所有不同的数的异或和。
对于“不同的数”一类问题,可以离线处理:
将询问按照r排序,用线段树维护区间异或和,然后从1到n扫一遍这个序列,如果a[i]出现过,就把之前出现的a[i]从线段树种删去(改成0即可),没出现则不管,然后把新的a[i]放进线段树(把第i位由0改成a[i])。
如果此时的i是要处理的询问的r,则此时线段树中的[l, r]异或和就是要求的“不同的数异或和”。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#include <map>
using namespace std;
const int N = 1000005;
int n, m;
int data[4 * N], a[N], ans[N], sum[N];
void change(int k, int l, int r, int p, int x){
if(l == r) return (void) (data[k] = x);
int mid = (l + r) >> 1;
if(p <= mid) change(k << 1, l, mid, p, x);
else change(k << 1 | 1, mid + 1, r, p, x);
data[k] = data[k << 1] ^ data[k << 1 | 1];
}
int query(int k, int l, int r, int ql, int qr){
if(ql <= l && qr >= r) return data[k];
int mid = (l + r) >> 1, ret = 0;
if(ql <= mid) ret ^= query(k << 1, l, mid, ql, qr);
if(qr > mid) ret ^= query(k << 1 | 1, mid + 1, r, ql, qr);
return ret;
}
struct question{
int id, l, r;
bool operator < (question b) const{
return r < b.r;
}
} q[N];
map <int, int> pos;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]), sum[i] = sum[i - 1] ^ a[i];
scanf("%d", &m);
for(int i = 1; i <= m; i++)
scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q + 1, q + m + 1);
for(int i = 1, qnum = 1; i <= n && qnum <= m; i++){
if(pos.find(a[i]) != pos.end()) change(1, 1, n, pos[a[i]], 0);
change(1, 1, n, i, a[i]);
pos[a[i]] = i;
while(i == q[qnum].r){
ans[q[qnum].id] = query(1, 1, n, q[qnum].l, q[qnum].r);
ans[q[qnum].id] ^= sum[q[qnum].r] ^ sum[q[qnum].l - 1];
qnum++;
}
}
for(int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
return 0;
}
水题!
居然!
T了!
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005;
ll n, k, a[N], b[N];
bool check(ll x){
ll cnt = 0;
int j = 0;
for(int i = n; i; i--){
while(j <= n && a[i] * b[j] < x) j++;
cnt += n - j + 1;
}
return cnt >= k;
}
int main(){
scanf("%lld%lld", &n, &k);
for(int i = 1; i <= n; i++)
scanf("%lld%lld", &a[i], &b[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
ll l = a[1] * b[1], r = a[n] * b[n], mid;
while(l < r){
mid = (l + r + 1) >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
printf("%lld\n", l);
return 0;
}
@ 数数完全不用一个一个数啊!!!
二维平面上N个点之间共有C(n,2)条连线。求这C(n,2)条线中斜率小于0的线的数量。
二维平面上的一个点,根据对应的X Y坐标可以表示为(X,Y)。例如:(2,3) (3,4) (1,5) (4,6),其中(1,5)同(2,3)(3,4)的连线斜率 < 0,因此斜率小于0的连线数量为2。
Input
第1行:1个数N,N为点的数量(0 <= N <= 50000)
第2 - N + 1行:N个点的坐标,坐标为整数。(0 <= X[i], Y[i] <= 10^9)
Output
输出斜率小于0的连线的数量。(2,3) (2,4)以及(2,3) (3,3)这2种情况不统计在内。
原来就是求逆序对啊!
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005;
int n;
int ans;
struct point{
int x, y;
bool operator < (point b) const{
return x < b.x;
}
} p[N], tmp[N];
bool cmp(point a, point b){
return a.y == b.y ? a.x < b.x : a.y < b.y;
}
void merge_sort(int l, int r){
if(l == r) return;
int mid = (l + r) >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int pa = l, pb = mid + 1, pt = l;
while(pa <= mid && pb <= r){
if(p[pb] < p[pa]) {
tmp[pt++] = p[pb++];
ans += mid - pa + 1;
}
else
tmp[pt++] = p[pa++];
}
while(pb <= r) tmp[pt++] = p[pb++];
while(pa <= mid) tmp[pt++] = p[pa++];
for(int i = l; i <= r; i++)
p[i] = tmp[i];
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d%d", &p[i].x, &p[i].y);
sort(p + 1, p + n + 1, cmp);
merge_sort(1, n);
printf("%d\n", ans);
return 0;
}
X轴上有N个点,求X轴上一点使它到这N个点的距离之和最小,输出这个最小的距离之和。
TODO: 证明它
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 10005;
int n;
ll a[N];
double x, ans;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
if(n & 1)
x = a[n / 2 + 1];
else
x = (double) (a[n / 2] + a[n / 2 + 1]) / 2;
for(int i = 1; i <= n; i++)
ans += fabs(x - a[i]);
printf("%.0lf\n", ans);
return 0;
}
你的朋友写下一串包含1和0的串让你猜,你可以从中选择一个连续的子串(例如其中的第3到第5个数字)问他,该子串中包含了奇数个还是偶数个1,他会回答你的问题,然后你可以继续提问......你怀疑朋友的答案可能有错,或说同他之前的答案相互矛盾,例如:1 - 2 奇数,3 - 4 奇数,那么可以确定1 - 4 一定是偶数,如果你的朋友回答是奇数,就产生了矛盾。给出所有你朋友的答案,请你找出第一个出现矛盾的答案。
Input
第1行:2个数N, Q,N为串的长度,Q为询问的数量。(2 <= N <= 100000, 2 <= Q <= 50000)
第2 - Q + 1行:每行包括两个数以及一个字符串来描述朋友的回答,2个数中间用空格分隔,分别表示区间的起点和终点,后面的字符为"even"或"odd",表示朋友的答案。
Output
输出1个数,表示朋友的答案中第一个错误答案的位置,如果所有答案均不矛盾,则输出-1。
可以把闭区间变成左开右闭区间,用带权并查集维护与父亲的关系即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100005;
int n, m, fa[N];
bool w[N];
char tmp[5];
int findfa(int x){
if(x == fa[x]) return x;
int anc = findfa(fa[x]);
w[x] ^= w[fa[x]];
fa[x] = anc;
return anc;
}
void join(int u, int v, bool op){
int fau = findfa(u), fav = findfa(v);
fa[fav] = fau;
w[fav] = op ^ w[u] ^ w[v];
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n + 1; i++)
fa[i] = i;
for(int i = 1, l, r, fal, far; i <= m; i++){
scanf("%d%d%s", &l, &r, tmp);
r++;
fal = findfa(l), far = findfa(r);
if(tmp[0] == 'e'){
if(fal != far) join(l, r, 0);
else if (w[l] != w[r]) {
printf("%d\n", i);
return 0;
}
}
else{
if(fal != far) join(l, r, 1);
else if(w[l] == w[r]){
printf("%d\n", i);
return 0;
}
}
}
printf("-1\n");
return 0;
}
给出一个包括N个元素的整数数组A,包括A本身在内,共有 (N+1)*N / 2个非空子段。例如:1 3 2的子段为{1} {3} {2} {1 3} {3 2} {1 3 2}。在这些子段中,如果最大值同最小值的差异不超过K,则认为这是一个合格的子段。给出数组A和K,求有多少符合条件的子段。例如:3 5 7 6 3,K = 2,符合条件的子段包括:{3} {5} {7} {6} {3} {3 5} {5 7} {7 6} {5 7 6},共9个。
Input
第1行:2个数N, K(1 <= N <= 50000, 0 <= K <= 10^9)
第2 - N + 1行:每行1个数,对应数组的元素Ai(0 <= A[i] <= 10^9)
Output
输出符合条件的子段数量。
扫一遍,把每个元素丢进一个能求最大值最小值的队列里,如果最大值减最小值大于K了就从队列左边弹出元素。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005;
int n, k, ans, a[N], qmin[N], qmax[N], l, r, lmin, rmin, lmax, rmax;
int main(){
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
lmin = lmax = l = 1, rmin = rmax = 0;
for(r = 1; r <= n; r++){
while(lmin <= rmin && a[qmin[rmin]] >= a[r]) rmin--;
qmin[++rmin] = r;
while(lmax <= rmax && a[qmax[rmax]] <= a[r]) rmax--;
qmax[++rmax] = r;
while(a[qmax[lmax]] - a[qmin[lmin]] > k && lmin <= rmin && lmax <= rmax){
if(qmin[lmin] == l) lmin++;
if(qmax[lmax] == l) lmax++;
l++;
}
ans += r - l + 1;
}
printf("%d\n", ans);
return 0;
}
给出N个正整数,找出N个数两两之间最大公约数的最大值。例如:N = 4,4个数为:9 15 25 16,两两之间最大公约数的最大值是15同25的最大公约数5。
Input
第1行:一个数N,表示输入正整数的数量。(2 <= N <= 50000)
第2 - N + 1行:每行1个数,对应输入的正整数.(1 <= S[i] <= 1000000)
Output
输出两两之间最大公约数的最大值。
哎……数论……还是看到题就不会……看了题解倒是会……可是有啥用呢……
答案范围小,由大到小枚举答案,再对于答案枚举它的1倍、2倍、3倍,数出来有多少数是它的倍数,如果数出来大于等于2,这个答案就是最终答案。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005, S = 1000005;
int n, cnt[S], maxs;
int main(){
scanf("%d", &n);
for(int i = 1, tmp; i <= n; i++)
scanf("%d", &tmp), cnt[tmp]++, maxs = max(maxs, tmp);
for(int i = maxs; i; i--){
int tot = 0;
for(int j = i; j <= maxs; j += i)
tot += cnt[j];
if(tot >= 2){
printf("%d\n", i);
return 0;
}
}
return 0;
}
我是抱着怎样的心情做这样一道题……
题面是腾讯打兔子……………………企鹅消灭兔子?!
有N只兔子,每只有一个血量B[i],需要用箭杀死免子。有M种不同类型的箭可以选择,每种箭对兔子的伤害值分别为D[i],价格为P[i](1 <= i <= M)。假设每种箭只能使用一次,每只免子也只能被射一次,计算要消灭地图上的所有兔子最少需要多少Q币。如不能杀死所有兔子,请输出No Solution。
特别说明:1、当箭的伤害值大于等于兔子的血量时,能将兔子杀死;2、血量B[i],箭的伤害值D[i],箭的价格P[i],均小于等于100000。
Input
第1行:两个整数N,M,中间用空格分隔(1 <= N, M <= 50000),分别表示兔子的个数和箭的种类。
第2 - N + 1行:每行1个正整数(共N行),表示兔子的血量B[i](1 <= B[i] <= 100000)。
第N + 2 - N + M + 1行:每行2个正整数(共M行),中间用空格分隔,表示箭所能造成的伤害值D[i],和需要花费的Q币P[i](1 <= D[i], P[i] <= 100000)。
Output
输出最少需要多少Q币才能消灭所有的兔子。如果不能杀死所有兔子,请输出"No Solution"。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int N = 50005;
int n, m, ans;
struct rabbit {
int b;
bool operator < (rabbit obj) const {
return b > obj.b;
}
} rabbits[N];
struct arrow {
int d, p;
bool operator < (arrow obj) const {
return p > obj.p;
}
} arrows[N];
bool cmp_d (arrow a, arrow b){
return a.d > b.d;
}
priority_queue <arrow> q;
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%d", &rabbits[i].b);
for(int i = 1; i <= m; i++)
scanf("%d%d", &arrows[i].d, &arrows[i].p);
sort(rabbits + 1, rabbits + n + 1);
sort(arrows + 1, arrows + m + 1, cmp_d);
for(int i = 1, j = 1; i <= n; i++){
while(j <= m && arrows[j].d >= rabbits[i].b){
q.push(arrows[j]);
j++;
}
if(q.empty()){
printf("No Solution\n");
return 0;
}
ans += q.top().p;
q.pop();
}
printf("%d\n", ans);
return 0;
}
给出1个M*N的矩阵M1,里面的元素只有0或1,找出M1的一个子矩阵M2,M2中的元素只有1,并且M2的面积是最大的。输出M2的面积。
Input
第1行:2个数m,n中间用空格分隔(2 <= m,n <= 500)
第2 - N + 1行:每行m个数,中间用空格分隔,均为0或1。
Output
输出最大全是1的子矩阵的面积。
枚举子矩阵的最下面一行,对每一位求向上的前缀和,枚举前缀和最小位,向左右延伸得到最大的以它为最小值的区间,区间宽度*这个最小值来更新答案。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 502;
int m, n, mp[N][N], sum[N], stk[N], top, toleft[N], toright[N], ans;
int main(){
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &mp[i][j]);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++)
sum[j] = mp[i][j] ? sum[j] + 1 : 0;
top = 0, stk[0] = 0;
for(int j = 1; j <= m; j++){
while(top && sum[stk[top]] >= sum[j]) top--;
toleft[j] = stk[top];
stk[++top] = j;
}
top = 0, stk[0] = m + 1;
for(int j = m; j; j--){
while(top && sum[stk[top]] >= sum[j]) top--;
toright[j] = stk[top];
stk[++top] = j;
}
for(int j = 1; j <= m; j++)
ans = max(ans, sum[j] * (toright[j] - toleft[j] - 1));
}
printf("%d\n", ans);
return 0;
}
一个数组的元素为1至N的整数,现在要对这个数组进行排序,在排序时只能将元素放在数组的头部或尾部,问至少需要移动多少个数字,才能完成整个排序过程?
例如:
2 5 3 4 1 将1移到头部 =>
1 2 5 3 4 将5移到尾部 =>
1 2 3 4 5 这样就排好了,移动了2个元素。
给出一个1-N的排列,输出完成排序所需的最少移动次数。
Input
第1行:1个数N(2 <= N <= 50000)。
第2 - N + 1行:每行1个数,对应排列中的元素。
Output
输出1个数,对应所需的最少移动次数。
只有最长的连续数字组成的序列不用动,所以把它求出来即可。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 50005;
int n, pos[N], ans;
int main(){
scanf("%d", &n);
for(int i = 1, tmp; i <= n; i++)
scanf("%d", &tmp), pos[tmp] = i;
for(int i = 1, last = 0; i <= n; i++)
ans = max(ans, last = (pos[i] > pos[i - 1]) ? last + 1 : 1);
printf("%d\n", n - ans);
return 0;
}
难死我了……数论只会GCD……
1/N! = 1/X + 1/Y(0 < x<=y),给出N,求满足条件的整数解的数量。例如:N = 2,1/2 = 1/3 + 1/6,1/2 = 1/4 + 1/4。由于数量可能很大,输出Mod 10^9 + 7。
Input
输入一个数N(1 <= N <= 1000000)。
Output
输出解的数量Mod 10^9 + 7。
所以把分解质因数然后乘法原理即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1000005, P = 1000000007;
ll n, cnt[N], ans = 1, prime[N], pcnt;
bool np[N];
void euler(){
np[1] = np[0] = 1;
for(int i = 2; i <= n; i++){
if(!np[i]) prime[++pcnt] = i;
for(int j = 1; j <= pcnt && i * prime[j] <= n; j++){
np[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
void factorize(int x){
for(int i = 1; prime[i] * prime[i] <= x; i++)
while(x % prime[i] == 0){
cnt[prime[i]] += 2;
x /= prime[i];
}
if(x > 1) cnt[x] += 2;
}
int main(){
scanf("%lld", &n);
euler();
for(int i = 2; i <= n; i++)
factorize(i);
for(int i = 2; i <= n; i++)
ans = ans * (cnt[i] + 1) % P;
ans = (1 + ans) * 500000004 % P;
printf("%lld\n", ans);
return 0;
}
今天啥都难……数论难完DP难……
N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
例如:-2 11 -4 13 -5 6 -2,分为2段,11 -4 13一段,6一段,和为26。
Input
第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 5000)
第2 - N+1行:N个整数 (-10^9 <= a[i] <= 10^9)
Output
输出这个最大和
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 5005, INF = 0x3f3f3f3f;
int n, m;
ll a[N], dp[N], pre[N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int j = 1; j <= m; j++){
dp[1] = a[1];
for(int i = 2; i <= n; i++){
dp[i] = max(dp[i - 1], pre[i - 1]) + a[i];
if(i > 1) pre[i - 1] = max(pre[i - 2], dp[i - 1]);
}
pre[n] = max(pre[n - 1], dp[n]);
}
printf("%lld\n", pre[n]);
return 0;
}
啊!时隔多天!又写线段树找虐了!
一棵有N个节点的树,每个节点对应1个编号及1个权值,有2种不同的操作。
操作1:S x y z,表示如果编号为x的节点的权值 < y,则将节点x的权值加上z。(Single)
操作2:A x y z,表示如果编号为x的节点以及其所有子节点的权值平均值 < y,则将节点x及其所有子节点的权值加上z。(All)
给出树节点之间的关系,进行M次操作,问所有操作完成后,各个节点的权值为多少?
节点的编号为0 - N - 1,根节点的编号为0,并且初始情况下,根节点的权值也是0。
Input
第1行:2个数N, M,N为节点的数量,M为操作的数量(1 <= N, M <= 50000)。
第2 - N行:每行描述一个节点N[i]的信息,第2行对应编号为1的节点,第N行对应编号为N - 1的节点。具体内容为:每行2个数P[i], W[i]。P[i]为当前节点的父节点的编号,W[i]为当前节点的权值。(0 <= W[i] <= 10^5, P[i] < i)
第N + 1 - N + M行:每行表示一个操作,S x y z或A x y z,(0 <= y, z <= 10^5)。
Output
输出共N行,每行1个数W[i],表示经过M次后,编号为0 - N - 1的节点的权值。
“前序遍历”一下,给每个点一个序列里的位置,然后正常线段树即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 50005;
int n, m, adj[N], nxt[N], ncnt, pos[N], sze[N];
ll w[N], a[N], data[4*N], lazy[4*N];
void add(int fa, int son){
nxt[son] = adj[fa];
adj[fa] = son;
}
void dfs(int u){
pos[u] = ++ncnt;
a[ncnt] = w[u];
sze[u] = 1;
for(int v = adj[u]; v; v = nxt[v])
dfs(v), sze[u] += sze[v];
}
void build(int k, int l, int r){
if(l == r) return (void) (data[k] = a[l]);
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
data[k] = data[k << 1] + data[k << 1 | 1];
}
void pushdown(int k, int l, int r){
if(l == r || !lazy[k]) return;
int mid = (l + r) >> 1;
lazy[k << 1] += lazy[k];
lazy[k << 1 | 1] += lazy[k];
data[k << 1] += lazy[k] * (mid - l + 1);
data[k << 1 | 1] += lazy[k] * (r - mid);
lazy[k] = 0;
}
void change(int k, int l, int r, int ql, int qr, ll x){
if(ql <= l && qr >= r)
return (void) (data[k] += (r - l + 1) * x, lazy[k] += x);
pushdown(k, l, r);
int mid = (l + r) >> 1;
if(ql <= mid) change(k << 1, l, mid, ql, qr, x);
if(qr > mid) change(k << 1 | 1, mid + 1, r, ql, qr, x);
data[k] = data[k << 1] + data[k << 1 | 1];
}
ll query(int k, int l, int r, int ql, int qr){
if(ql <= l && qr >= r) return data[k];
pushdown(k, l, r);
int mid = (l + r) >> 1;
ll ret = 0;
if(ql <= mid) ret += query(k << 1, l, mid, ql, qr);
if(qr > mid) ret += query(k << 1 | 1, mid + 1, r, ql, qr);
return ret;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 2, fa; i <= n; i++)
scanf("%d%lld", &fa, &w[i]), add(fa + 1, i);
dfs(1);
build(1, 1, n);
char s[3];
ll x, y, z;
while(m--){
scanf("%s%lld%lld%lld", s, &x, &y, &z);
x++;
if(s[0] == 'S'){
if(query(1, 1, n, pos[x], pos[x]) < y)
change(1, 1, n, pos[x], pos[x], z);
}
else{
if(query(1, 1, n, pos[x], pos[x] + sze[x] - 1) < y * sze[x])
change(1, 1, n, pos[x], pos[x] + sze[x] - 1, z);
}
}
for(int i = 1; i <= n; i++)
printf("%lld\n", query(1, 1, n, pos[i], pos[i]));
return 0;
}
@ 不开longlong见祖宗……开了longlong中间写了个int仍然见祖宗……
据说可以用某种神奇的并查集过,然鹅我太蒻了,用二分答案,也能过……
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005, INF = 0x3f3f3f3f;
int n, fa[N], adj[N], nxt[N], cap[N], w[N], sze[N];
void add(int u, int v){
nxt[v] = adj[u];
adj[u] = v;
}
bool check(int u){
sze[u] = w[u];
for(int v = adj[u]; v != -1; v = nxt[v]){
if(check(v)) return 1; //下面的断了
sze[u] += sze[v];
}
if(sze[u] > cap[u]) return 1; //断了
return 0;
}
int main(){
scanf("%d", &n);
cap[0] = INF;
for(int i = 1; i <= n; i++)//绳子编号改为1~n,根节点为0
scanf("%d%d%d", &cap[i], &w[i], &fa[i]), fa[i]++;
int l = 1, r = n, mid;
while(l < r){
mid = (l + r + 1) >> 1;
memset(adj, -1, sizeof(adj));
memset(sze, 0, sizeof(sze));
for(int i = 1; i <= mid; i++)
add(fa[i], i);
if(check(0)) r = mid - 1;//断了
else l = mid;//没断
}
printf("%d\n", l);
return 0;
}
排序后(每个指针居然是等价的!)用最小表示法表示这个时钟每两个相邻指针之间的距离,然后暴力判断是否相同就可以了。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 503;
int n, m, p, a[N][N], st[N];
ll ans;
int mini_rep(int *s){
int i = 0, j = 1, k = 0;
while(i < m && j < m && k < m){
k = 0;
while(s[i + k] == s[j + k] && k < m) k++;
if(k == m) return i;
if(s[i+k]>s[j+k])
if(i+k+1>j) i=i+k+1;
else i=j+1;
else if(j+k+1>i) j=j+k+1;
else j=i+1;
}
return i < m ? i : j;
}
bool cmp(int i, int j){
for(int k = 0; k < m; k++)
if(a[i][(st[i] + k) % m] != a[j][(st[j] + k) % m])
return 0;
return 1;
}
int main(){
scanf("%d%d%d", &n, &m, &p);
for(int i = 1; i <= n; i++){
for(int j = 0; j < m; j++)
scanf("%d", &a[i][j]);
sort(a[i], a[i] + m);
int tmp = a[i][m - 1];
for(int j = m - 1; j >= 0; j--)
a[i][j] -= a[i][j - 1];
a[i][0] = a[i][0] - tmp + p;
st[i] = mini_rep(a[i]);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j < i; j++)
if(cmp(i, j)) ans++;
printf("%lld\n", ans);
return 0;
}
因为1号流水线随时都能用,只要使2号尽量紧凑即可,所以……
将所有物品分成两组,1号时间小于2号一组,1号时间大于2号一组,先搞前者,按1号时间升序排序,然后搞后者,按2号时间降序排序。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 50005;
struct node {
int a, b;
} s1[N], s2[N];
int n, cnt1, cnt2;
long long ans1, ans2;
bool cmp1(node x, node y){ return x.a < y.a; }
bool cmp2(node x, node y){ return x.b > y.b; }
int main(){
scanf("%d", &n);
for(int i = 1, a, b; i <= n; i++){
scanf("%d%d", &a, &b);
if(a <= b) s1[++cnt1].a = a, s1[cnt1].b = b;
else s2[++cnt2].a = a, s2[cnt2].b = b;
}
sort(s1 + 1, s1 + cnt1 + 1, cmp1);
sort(s2 + 1, s2 + cnt2 + 1, cmp2);
for(int i = 1; i <= cnt1; i++){
ans1 += s1[i].a;
ans2 = max(ans2, ans1) + s1[i].b;
}
for(int i = 1; i <= cnt2; i++){
ans1 += s2[i].a;
ans2 = max(ans2, ans1) + s2[i].b;
}
printf("%lld\n", ans2);
return 0;
}
很简单的二分,注意可能答案为0!!!
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 50005, INF = 0x3f3f3f3f;
int n, a[N], p[N], cnt;
bool check(int k){
int tot = 0, last = -INF;
for(int i = 1; i <= cnt; i++){
if(p[i] - last >= k)
tot++, last = p[i];
if(tot >= k) return 1;
}
return 0;
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 2; i < n; i++)
if(a[i] > a[i - 1] && a[i] > a[i + 1])
p[++cnt] = i;
int l = 0, r = n, mid;
while(l < r){
mid = (l + r + 1) >> 1;
if(check(mid))//可以放得下
l = mid;
else
r = mid - 1;
}
printf("%d\n", l);
return 0;
}
维护一个栈,从左往右,如果栈顶元素比当前元素大,且以后还会出现,则弹出。
注意栈中元素不能重复,且根本不打算放进去的元素是不能用来把别的元素弹出去的。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100005;
char s[N], stk[28];
int n, last[128], top;
bool vis[128];
int main(){
scanf("%s", s + 1);
n = strlen(s + 1);
for(int i = n; i; i--)
if(!last[s[i]]) last[s[i]] = i;
for(int i = 1; i <= n; i++){
if(vis[s[i]]) continue;
while(top && stk[top] > s[i] && last[stk[top]] > i)
vis[stk[top--]] = 0;
vis[stk[++top] = s[i]] = 1;
}
for(int i = 1; i <= top; i++)
printf("%c", stk[i]);
puts("");
return 0;
}
@ 读入优化里面没初始化,遂跪……
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
int qread(){
char c; bool minus = 0; int ret = 0;
while((c = getchar()) > '9' || c < '0')
if(c == '-') minus = 1;
ret = c - '0';
while((c = getchar()) <= '9' && c >= '0')
ret = ret * 10 + c - '0';
return minus ? -ret : ret;
}
const int N = 300005;
int n, w[N], p;
ll ans;
int main(){
n = qread();
for(int i = 1; i <= n; i++)
w[i] = qread();
p = qread();
for(int i = 1, a, b; i <= p; i++){
a = qread(), b = qread();
ans = 0;
for(int j = a; j <= n; j += b)
ans += w[j];
printf("%lld\n", ans);
}
return 0;
}
心态崩了……
有一个可以在二维平面上做跳跃的机器人,该机器人有独特的跳跃程序。该程序的跳跃距离是由一个循环序列S决定的。序列S有无穷多项,但其有一个最小周期序列,令其为A,A中有N个元素(N<=50),S[i]=A[i mod N],i从0取到正无穷。例如,A={2,5,1,1}那么S={2,5,1,1,2,5,1,1,2,5,1,1.....}。机器人的跳跃规则如下:第k次跳跃时,机器人可以跳出离原来位置S[k]的距离外,方向随意,其中k=0,1,2,3,...。这个方向是由机器人的控制玩家自己决定的,它可以是0~360度内的任意一个方向,角度未必是整数,落点也未必是整点。注意,控制玩家不能控制跳跃的距离,这个距离是机器人的自身属性即A序列决定的;玩家只能决定每一次跳跃的方向。现在,你需要让机器人从平面的(0,0)处跳跃到(x,0)处,求出最少需要控制机器人跳跃几次。其中,初始(第0次)跳跃距离恰为S[0]。
例如:x=5,A={3,4},(0,0)->(9/5,12/5)->(5,0)。
Input
第一行一个整数T,表示测试数据的数量,1<=T<=10。
接下来有T组相同格式的数据。
每组数据的第一行包含两个整数x、N,其中 -1,000,000,000<=x<=1,000,000,000 , 1<=N<=50。
接下来N行,每行一个整数A[i],其中1<=A[i]<=1,000,000,000。
Output
每组测试数据输出一行一个整数,即能使机器人完成任务最少跳跃的次数。
如果跳过两轮以上,则如果跳过的总路程 >= x 则一定可行:把两轮分别走成直线,摆成等腰三角形的腰,x作为底,能摆出[0, 2*L]之间的所有距离,那么只要总路程 >= x 且跳过两轮以上,把最后两轮摆成等腰三角形即可。
如果不到两轮,则如果跳过总路程 >= x 且跳过总路程+x大于跳过的最长边的二倍即可(这样至少能构成三角形)。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 53;
int T, x, n;
ll a[N];
int main(){
scanf("%d", &T);
while(T--){
bool done = 0;
scanf("%d%d", &x, &n);
if(x < 0) x = -x;
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
if(!x){
printf("0\n");
continue;
}
ll sum = 0, maxn = 0;
for(int i = 1; i <= 2 * n; i++){
sum += a[(i - 1) % n + 1];
maxn = max(maxn, a[(i - 1) % n + 1]);
if(sum >= x && sum + x >= 2 * maxn){
printf("%d\n", i);
done = 1;
break;
}
}
if(done) continue;
sum /= 2;
ll ans = x / sum, tot = 0;
x %= sum;
if(!x) printf("%lld\n", ans * n);
else
for(int i = 1; i <= n; i++){
tot += a[i];
if(tot >= x){
printf("%lld\n", ans * n + i);
break;
}
}
}
return 0;
}
这题跟个智障一样
可惜我也跟个智障一样
zzetza
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 35, P = 1000000007;
ll n;
struct matrix {
ll g[N][N];
matrix(){
memset(g, 0, sizeof(g));
}
matrix operator * (matrix b){
matrix c;
for(int k = 1; k < N; k++)
for(int i = 1; i < N; i++)
for(int j = 1; j < N; j++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % P) % P;
return c;
}
} ans, op;
matrix qpow(matrix a, ll x){
matrix ret;
for(int i = 1; i < N; i++)
ret.g[i][i] = 1;
while(x){
if(x & 1) ret = ret * a;
a = a * a;
x >>= 1;
}
return ret;
}
int main(){
scanf("%lld", &n);
n *= 10;
if(n <= (ll) 40){
printf("1\n");
return 0;
}
for(int i = 1; i < N; i++)
ans.g[i][1] = 1;
for(int i = 1; i < N - 1; i++)
op.g[i][i + 1] = 1;
op.g[N - 1][1] = op.g[N - 1][N - 10] = 1;
ans = qpow(op, n - 40) * ans;
printf("%lld\n", ans.g[N - 1][1]);
return 0;
}
随手记一下(五)
给定一个01串S,求出它的一个尽可能长的子串S[i..j],满足存在一个位置i<=x Input
一行包含一个只由0和1构成的字符串S。 S的长度不超过1000000。
Output
一行包含一个整数,表示满足要求的最长子串的长度。
看了一眼讨论区,感觉有些地方讲得不是很清楚?
可以这样考虑:
设g[x]为[1, x]中0的个数,h[x]为[1, x]中1的个数。
题目要求的可以描述为:一个区间(l, r]中有一个元素x (l < x < r), 满足(l, x]中0的个数大于1的个数,(x, r]中0的个数小于1的个数。
由“(l, x]中0的个数大于1的个数”可以写出:
g[x] - g[l] > h[x] - h[l]
移项得:
h[x] - g[x] < h[l] - g[l]
由“(x, r]中0的个数小于1的个数”可以写出:
g[r] - g[x] < h[r] - h[x]
移项得:
h[x] - g[x] < h[r] - g[r]
我们设f[x] = h[x] - g[x],则可得:
f[x] < f[l], f[x] < f[r]
那么我们把整个f[x]数组求出来(这个数组中的每个元素要么比前一个多1要么比前一个少1),对于每个x,向左找到最靠左的、f[l] > f[x] 的 l,向右找到最靠右的、f[r] > f[x] 的 r,则 r - l 为区间的长度。
那么需要满足x左右都能找到f比它大的元素,并且只要满足这一条,x越小越好。
那么在序列左侧找到第一个f[i] > f[i + 1]的元素,在右侧找到第一个f[j] > f[j - 1]的元素,如果(i, j]存在,在[i, j]中找最小值x,找到后在整个序列的左、右侧分别找第一个f 大于 f[x]的元素,就是题目所求区间。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1000005, INF = 0x3f3f3f3f;
int n, f[N];
int main(){
char c;
while(1){
c = getchar();
if(c == '0') n++, f[n] = f[n - 1] - 1;
else if(c == '1') n++, f[n] = f[n - 1] + 1;
else break;
}
int l = 0, r = n;
while(l < n && f[l] < f[l + 1]) l++;
while(r && f[r] < f[r - 1]) r--;
if(r <= l){
printf("0\n");
return 0;
}
int minn = INF;
for(int i = l + 1; i <= r; i++)
minn = min(minn, f[i]);
for(l = 0; f[l] <= minn; l++);
for(r = n; f[r] <= minn; r--);
printf("%d\n", r - l);
return 0;
}
白克喜欢找一个序列中的次大值。对于一个所有数字都不同的序列 ,他的次大值是最大的 xj ,并且满足
对于一个所有数字都不同的序列 ,他的幸运数字是最大值和次大值的异或值(Xor)。
现在有一个序列 。 表示子段 。你的任务是找出所有子段的最大幸运数字。
注意,序列s中的所有数字都是不同的。
从左到右扫一遍,维护单调递减栈,要把一个新元素放进去的时候,它在栈中的前一个元素可以作为区间最大值,新元素作为区间次大值;但是从左到右扫一遍只能求最大值在左、次大值在右的情况,从右到左再扫一遍,就能求出所有情况了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
int read(){
char c;
while((c = getchar()) < '0' || c > '9');
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
const int N = 100005;
int n, a[N], stk[N], top, ans;
int main(){
while(~scanf("%d", &n)){
for(int i = 1; i <= n; i++)
a[i] = read();
top = 0;
for(int i = 1; i <= n; i++){
while(top && stk[top] < a[i]) top--;
ans = max(ans, stk[top] ^ a[i]);
stk[++top] = a[i];
}
top = 0;
for(int i = n; i; i--){
while(top && stk[top] < a[i]) top--;
ans = max(ans, stk[top] ^ a[i]);
stk[++top] = a[i];
}
printf("%d\n", ans);
}
return 0;
}
取三个颜色a、b、c,只要每行错开着循环涂这三个颜色,即使图全都是X也可以涂完,答案不大于3。
在此基础上,如果没有奇环,两种颜色即可。
如果点都是独立的,一种颜色即可。
搜一搜就好了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 55;
int T, n;
char tmp[N];
bool X[N][N], vis[N][N], col[N][N];
const int dx[] = {0, -1, -1, 0, 1, 1, 0};
const int dy[] = {0, 0, 1, 1, 0, -1, -1};
bool legal(int i, int j){
return X[i][j] && i && j && i <= n && j <= n;
}
bool dfs(int x, int y, bool color){
vis[x][y] = 1, col[x][y] = color;
int vx, vy;
for(int d = 1; d <= 6; d++){
vx = x + dx[d], vy = y + dy[d];
if(!legal(vx, vy)) continue;
if(!vis[vx][vy]) {
if(dfs(vx, vy, !color))
return 1;
}
else{
if(col[vx][vy] == color)
return 1;
}
}
return 0;
}
int main(){
scanf("%d", &T);
while(T--){
memset(vis, 0, sizeof(vis));
memset(col, 0, sizeof(col));
//memset(X, 0, sizeof(X));
bool empty = 1;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%s", tmp + 1);
for(int j = 1; j <= n; j++)
if(tmp[j] == 'X') X[i][j] = 1, empty = 0;
else X[i][j] = 0;
}
if(empty){
printf("0\n");
goto LOOP;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(X[i][j] && !vis[i][j] && dfs(i, j, 0)){
printf("3\n");
goto LOOP;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(col[i][j]){
printf("2\n");
goto LOOP;
}
printf("1\n");
LOOP: continue;
}
return 0;
}
@ 输出优化没有特判0,遂GG……
以后一定要每天写一遍输入输出优化……
有一天,小a给了小b一些数字,让小b帮忙找到其中最大的数,由于小b是一个程序猿,当然写了一个代码很快的解决了这个问题。
这时,邪恶的小c又出现了,他问小b,假如我只需要知道这些数字中的某个区间的最大值,你还能做嘛?
小b经过七七四十九天的思考,终于完美的解决了这道题目,这次,他想也让小c尝尝苦头,于是他问小c,我现在想知道存在多少不同的区间的最大值大于等于k,你还能做吗?
这次,小c犯了难,他来请教身为程序猿的你。
Hint:一个区间指al,al+1,…,ar这一段的数且l<=r,一个区间的最大值指max{al,al+1,…,ar},两个区间不同当且仅当[l1,r1],[l2,r2]中l1不等于l2或r1不等于r2Input
第一行读入一个正整数n(1<=n<=100000),表示有n个数字。
接下来一行读入n个正整数ai(1<=ai<=100000)
接下来一行一个正整数Q(1<=Q<=100000),表示有Q组询问。
接下来Q行,每行一个正整数k(1<=k<=100000)
Output
Q行,每行一个正整数,表示存在多少区间大于等于k。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
int read(){
char c;
while((c = getchar()) < '0' || c > '9');
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void write(ll x){
if(x == 0) {
putchar('0');
putchar('\n');
return;
}
char c[20];
int p = 0;
while(x) c[p++] = x % 10 + '0', x /= 10;
while(p--) putchar(c[p]);
putchar('\n');
}
const int N = 100005, INF = 0x3f3f3f3f;
int n, q, a[N], stk[N], top, l[N], r[N], k;
ll cnt[N];
int main(){
n = read();
for(int i = 1; i <= n; i++)
a[i] = read(), r[i] = n + 1;
a[0] = a[n + 1] = INF;
for(int i = 1; i <= n; i++){
while(top && a[stk[top]] <= a[i]) r[stk[top--]] = i;
l[i] = stk[top];
stk[++top] = i;
}
for(int i = 1; i <= n; i++)
cnt[a[i]] += (ll) (i - l[i]) * (r[i] - i);
for(int i = 99999; i; i--)
cnt[i] += cnt[i + 1];
q = read();
while(q--){
k = read();
write(cnt[k]);
}
return 0;
}
给定一棵无根树,假设它有n个节点,节点编号从1到n, 求任意两点之间的距离(最短路径)之和。
Input
第一行包含一个正整数n (n <= 100000),表示节点个数。
后面(n - 1)行,每行两个整数表示树的边。
Output
每行一个整数,第i(i = 1,2,...n)行表示所有节点到第i个点的距离之和。
居然卡着时限过了……
先求出每个点到根节点距离之和,然后对每个点O(1)从父节点转移答案即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 100005;
int read(){
char c;
while((c = getchar()) < '0' && c > '9');
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
int n;
int ecnt, go[2*N], nxt[2*N], adj[N];
int sze[N], dep[N];
ll ans[N];
void add(int u, int v){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
void dfs(int u, int pre){
sze[u] = 1;
ans[1] += dep[u];
for(int e = adj[u]; e; e = nxt[e])
if(go[e] != pre){
dep[go[e]] = dep[u] + 1;
dfs(go[e], u);
sze[u] += sze[go[e]];
}
}
void change(int u, int pre){
if(u != 1) ans[u] = ans[pre] + n - 2 * sze[u];
for(int e = adj[u]; e; e = nxt[e])
if(go[e] != pre)
change(go[e], u);
}
int main(){
n = read();
for(int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u, v), add(v, u);
dfs(1, 0);
change(1, 0);
for(int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}
我们的目的是在[1, n]中找到最大的三个互质的数。
首先,显然 n 和 n - 1 一定互质。
如果 n 是奇数,那么显然 n 和 n - 2 一定互质,直接输出 n * (n - 1) * (n - 2)。
否则 n 是偶数,n、n - 1、n - 2 的最小公倍数是 n * (n - 1) * (n - 2) / 2,是否有比这个数更大的呢?有:如果 n 和 n - 3 互质的话,n * (n - 1) * (n - 3)是一个可行解;如果 n 和 n - 3 不互质的话,(n - 1) * (n - 2) * (n - 3) 是一个可行解。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
ll n;
ll gcd(ll a, ll b){
return b ? gcd(b, a % b) : a;
}
int main(){
while(~scanf("%lld", &n)){
if(n < 3) printf("%lld\n", n);
else if(n & 1) printf("%lld\n", n * (n - 1) * (n - 2));
else if(gcd(n, n - 3) == 1) printf("%lld\n", n * (n - 1) * (n - 3));
else printf("%lld\n", (n - 1) * (n - 2) * (n - 3));
}
return 0;
}
dp[i][j]表示节点数为i、深度为j
dp[i][j] = dp[k][j - 1] * dp[i - 1 - k][j - 1] + 2 * dp[k][j - 2] * dp[i - 1 - k][j - 1]
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2005, J = 16, P = 1000000007;
int n = 2000;
ll dp[N][J], ans;
int main(){
dp[0][0] = dp[1][1] = 1;
for(int i = 2; i <= n; i++)
for(int j = 2; j <= 15; j++)
for(int k = 0; k < i; k++){
dp[i][j] = (dp[i][j] + dp[k][j - 1] * dp[i - k - 1][j - 1] % P) % P;
dp[i][j] = (dp[i][j] + 2 * dp[k][j - 2] * dp[i - k - 1][j - 1] % P) % P;
}
while(~scanf("%d", &n)){
ans = 0;
for(int j = 1; j <= 15; j++)
ans = (ans + dp[n][j]) % P;
printf("%lld\n", ans);
}
return 0;
}
X是一个n位数的正整数 (x=a0a1...an−1)
现在定义 F(x) 比如F(135)=1!*3!*5!=720.
我们给定一个n位数的整数X(至少有一位数大于1,X中可能有前导0),
然后我们去找一个正整数(s)符合以下条件:
1.这个数尽可能大,
2.这个数中不能含有数字0或1。
3.F(s)=F(x)
每个数分解德越长越好。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <ctime>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 105;
const int tb[10][10] = {
{0},
{0},
{1, 2},
{1, 3},
{3, 3, 2, 2},
{1, 5},
{2, 5, 3},
{1, 7},
{4, 7, 2, 2, 2},
{4, 7, 3, 3, 2}
};
ll n;
int cnt, s[N];
int main(){
scanf("%lld%lld", &n, &n);
while(n){
int t = n % 10;
for(int i = 1; i <= tb[t][0]; i++)
s[++cnt] = tb[t][i];
n /= 10;
}
sort(s + 1, s + cnt + 1);
for(int i = cnt; i; i--)
putchar(s[i] + '0');
putchar('\n');
return 0;
}
大家都熟悉堆栈操作。一个堆栈一般有两种操作,push和pop。假设所有操作都是合法的并且最终堆栈为空。我们可以有很多方法记录堆栈的操作,
(1) 对每个pop操作,我们记录它之前一共有多少个push操作。
(2) 对每个pop操作,我们记录这个被Pop的元素曾经被压上了几个。
例如:操作push, push, pop, push, push, pop, push, pop, pop, pop
用第一种方法 记录为 2, 4, 5, 5, 5
用第二种方法 记录为 0, 0, 0, 2, 4
这两种记录方法可以互相转化,我们的问题是,给定第二种记录方法的序列,请求出第一种记录方法的序列。
对于每个pop,根据它被压了几个,可以知道它是什么时候push的(是在哪个pop之前push的),然后求前缀和即可。
#include <stdio.h>
int read(){
bool op = 0;
char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') op = 1;
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 1000005;
int n, cnt[N];
int main(){
n = read();
for(int i = 1; i <= n; i++)
cnt[i - read()]++;
for(int i = 1; i <= n; i++){
cnt[i] += cnt[i - 1];
printf("%d ", cnt[i]);
}
putchar('\n');
return 0;
}
当给定一个序列a[0],a[1],a[2],...,a[n-1] 和一个整数K时,我们想找出,有多少子序列满足这么一个条件:把当前子序列里面的所有元素乘起来恰好等于K。
样例解释:
对于第一个数据,我们可以选择[3]或者[1(第一个1), 3]或者[1(第二个1), 3]或者[1,1,3]。所以答案是4。
K虽然很大,但是根据超强的学姐的估计上界和随机,K的约数一般不超过700个!
那么我们把约数求出来,放在lst数组中,然后dp[i][j]表示前i个数组成的乘积为lst[j]的子序列数目。
那么:
dp[i][j] = dp[i - 1][j];
if(lst[j] % a[i] == 0) dp[i][j] += dp[i - 1][lst[j] / a[i]];
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int read(){
bool op = 0;
char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') op = 1;
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 1005, M = 20005, P = 1000000007;
int T, n, k, a[N], lst[M], dp[M];
int main(){
T = read();
while(T--){
memset(dp, 0, sizeof(dp));
n = read(), k = read();
//printf("%d %d\n", n, k);
int cnt = 0;
for(int i = 1; (ll) i*i <= (ll) k; i++){
if(k % i == 0){
lst[++cnt] = i;
if(i*i < k) lst[++cnt] = k / i;
}
}
sort(lst + 1, lst + cnt + 1);
for(int i = 1; i <= n; i++)
a[i] = read();
dp[1] = 1;
for(int i = 1; i <= n; i++)
for(int j = cnt; j; j--){
if(lst[j] % a[i] == 0)
dp[j] = (dp[j] + dp[lower_bound(lst + 1, lst + cnt + 1, lst[j] / a[i]) - lst]) % P;
}
printf("%d\n", dp[cnt]);
}
return 0;
}
@ 滚动数组……忘了把j反过来了………………GG………………
给出一个1至N的排列,允许你做不超过K次操作,每次操作可以将相邻的两个数交换,问能够得到的字典序最大的排列是什么?
例如:N = 5, {1 2 3 4 5},k = 6,在6次交换后,能够得到的字典序最大的排列为{5 3 1 2 4}。
Input
第1行:2个数N, K中间用空格分隔(1 <= N <= 100000, 0 <= K <= 10^9)。
第2至N + 1行:每行一个数i(1 <= i <= N)。
Output
输出共N行,每行1个数,对应字典序最大的排列的元素。
我好菜啊……写了两个线段树……
每次把能移到最左侧的数中最大的移到最左侧即可。
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int read(){
bool op = 0;
char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') op = 1;
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 100005;
int n, m;
int a[N], pos[N], data[4 * N], ans[N];
int sum[4*N];
//pos[i]: i在原序列中的位置
void build(int k, int l, int r){
if(l == r) return (void)(data[k] = a[l]);
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
data[k] = max(data[k << 1], data[k << 1 | 1]);
}
int query(int k, int l, int r, int ql, int qr){
if(ql <= l && qr >= r) return data[k];
int mid = (l + r) >> 1, ret = 0;
if(ql <= mid) ret = max(ret, query(k << 1, l, mid, ql, qr));
if(qr > mid) ret = max(ret, query(k << 1 | 1, mid + 1, r, ql, qr));
return ret;
}
void remove(int k, int l, int r, int p){
if(l == r) return (void)(data[k] = 0);
int mid = (l + r) >> 1;
if(p <= mid) remove(k << 1, l, mid, p);
else remove(k << 1 | 1, mid + 1, r, p);
data[k] = max(data[k << 1], data[k << 1 | 1]);
}
void build2(int k, int l, int r){
if(l == r) return (void)(sum[k] = 1);
int mid = (l + r) >> 1;
build2(k << 1, l, mid);
build2(k << 1 | 1, mid + 1, r);
sum[k] = sum[k << 1] + sum[k << 1 | 1];
}
void remove2(int k, int l, int r, int p){
if(l == r) return (void)(sum[k] = 0);
int mid = (l + r) >> 1;
if(p <= mid) remove2(k << 1, l, mid, p);
else remove2(k << 1 | 1, mid + 1, r, p);
sum[k] = sum[k << 1] + sum[k << 1 | 1];
}
int findxth(int k, int l, int r, int x){ //找到现存的第x个数
if(l == r) return l;
int mid = (l + r) >> 1;
if(sum[k << 1] >= x) return findxth(k << 1, l, mid, x);
return findxth(k << 1 | 1, mid + 1, r, x - sum[k << 1]);
}
int count(int k, int l, int r, int ql, int qr){ //找到序列中[ql, qr]还有多少剩的
if(ql <= l && qr >= r) return sum[k];
int mid = (l + r) >> 1, ret = 0;
if(ql <= mid) ret += count(k << 1, l, mid, ql, qr);
if(qr > mid) ret += count(k << 1 | 1, mid + 1, r, ql, qr);
return ret;
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; i++)
a[i] = read(), pos[a[i]] = i;
build(1, 1, n);
build2(1, 1, n);
for(int t = 1; t <= n; t++){
ans[t] = query(1, 1, n, 1, findxth(1, 1, n, min(sum[1], m + 1)));
int p = pos[ans[t]];
remove(1, 1, n, p);
remove2(1, 1, n, p);
m -= count(1, 1, n, 1, p);
}
for(int i = 1; i <= n; i++)
printf("%d\n", ans[i]);
return 0;
}
有n个整数。输出他之中和x相与之后结果为x的有多少个。x从0到1,000,000
Input
第一行输入一个整数n。(1<=n<=1,000,000).
第二行有n个整数a[0],a[1],a[2],...a[n-1],以空格分开.(0<=a[i]<=1,000,000)
Output
对于每一组数据,输出1000001行,第i行对应和i相与结果是i的有多少个数字。
一开始想先枚举i(数)再枚举j(位),然后重复了……
其实先枚举j后枚举i就可以了。
#include <cstdio>
using namespace std;
int read(){
char c; bool op = 0;
while((c = getchar()) < '0' || c > '9')
if(c == '-') op = 1;
int ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void write(int x){
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 1000000;
int n, cnt[N + 3];
int main(){
n = read();
for(int i = 1; i <= n; i++)
cnt[read()]++;
for(int j = 19; j >= 0; j--)
for(int i = N; i; i--)
if(i >> j & 1)
cnt[i ^ (1 << j)] += cnt[i];
for(int i = 0; i <= N; i++)
write(cnt[i]), putchar('\n');
return 0;
}
任何正整数都能分解成2的幂,给定整数N,求N的此类划分方法的数量!由于方案数量较大,输出Mod 1000000007的结果。
比如N = 7时,共有6种划分方法。7=1+1+1+1+1+1+1
=1+1+1+1+1+2
=1+1+1+2+2
=1+2+2+2
=1+1+1+4
=1+2+4
dp[1] = 1;
if(i & 1) dp[i] = dp[i - 1]; //奇数,必有单独的1
else dp[i] = dp[i - 1] + dp[i / 2]; //偶数,前一种情况有单独的1,后一种情况没有单独的1
#include <cstdio>
using namespace std;
const int N = 1000005, P = 1000000007;
int n, f[N];
int main(){
scanf("%d", &n);
f[1] = 1;
for(int i = 2; i <= n; i += 2)
f[i + 1] = f[i] = (f[i - 1] + f[i/2]) % P;
printf("%d\n", f[n]);
return 0;
}