9.8-9.9多校互测与牛客网提高一测
多校互测
貌似是比较尴尬,本来100+40+30 (->) 70+30+30 。原因竟然是(dots)
貌似是只做了(day1)至于做不做(day2)还是两说,但是先写题解为妙。
A
这是一道语文题,出题人毒瘤到水题不能告诉你这题目是什么意思(dots)
然后考试的时候读了好长时间的题面,硬是没想出来。
然后考试最后半小时终于看懂了题目什么意思,但就是不想写正解了,于是索性打个暴力,更气人的是,暴力打完还剩(25min),还是不想写正解,于是想优化就加上了离散化代码到达了(50)行。考完试才知道正解(20)行。
于是贴一下我(70)分离散化代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define LL long long
using namespace std;
struct edge {
int id,a,ans;
} e[100001];
int n,b[100001],cnt,vis[100001],d[100001];
bool cmp(edge x,edge y) {
return x.a<y.a;
}
bool cml(edge x,edge y) {
return x.id<y.id;
}
void calc(int x) {
int p=lower_bound(b+1,b+1+cnt,e[x].a)-b;
int ans=0;
for(int i=p-1; i>=1; i--) {
if(e[x].a%b[i]==0)ans+=vis[i];
if(b[i]==0)break;
}
ans+=vis[p]-1;
e[x].ans=ans;
}
int main() {
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&e[i].a),e[i].id=i,e[i].ans=0;
sort(e+1,e+1+n,cmp);
for(int i=1; i<=n; i++) {
if(e[i].a!=e[i-1].a)b[++cnt]=e[i].a;
vis[cnt]++;
}
for(int i=1; i<=n; i++)d[i]=lower_bound(b+1,b+1+cnt,e[i].a)-b;
for(int i=1; i<=n; i++)calc(i);
sort(e+1,e+1+n,cml);
for(int i=1; i<=n; i++)printf("%d
",e[i].ans);
return 0;
}
B
就是给一段序列,然后求字母的出现次数最大的减出现次数最小的
我一开始就想,怎么样才能存储字母呢,我想了个自以为不错的方法——树状数组(最后发现前缀和统计即可)
//30pts
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
int vis[27],tot,tree[27][1000002],n,fin[27],answ;
int to_num(char s){
return s-96;
}
int lowbit(int k){
return k&(-k);
}
void add(int id,int x,int y){
while(x<=n){
tree[id][x]+=y;
x+=lowbit(x);
}
}
int ask(int id,int x){
int ans=0;
while(x!=0){
ans+=tree[id][x];
x-=lowbit(x);
}
return ans;
}
void work(int l,int r){
int maxn=0,minn=0x7fffffff;
for(int i=1;i<=tot;i++){
int p=ask(fin[i],r)-ask(fin[i],l-1);
if(p==0)continue;
maxn=max(maxn,p);
minn=min(minn,p);
}
answ=max(answ,maxn-minn);
}
int main()
{
// freopen("B.in","r",stdin);
// freopen("B.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++){
char s;
cin >> s;
int p=to_num(s);
if(!vis[p]){
vis[p]=1;
fin[++tot]=p;
}
add(p,i,1);
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
work(i,j);
printf("%d",answ);
return 0;
}
意思就是枚举左右端点,然后跑树状数组即可
但是我又通过询问李小浪,(Get)到(60pts)的方法:
用前缀和统计,然后枚举两个字符,然后再枚举每个数字在哪个区间出现的次数。也就是说用前缀和表示为((a_j-a_i)-(b_j-b_i))的值最小对吧,然后换一下形式((a_j-b_j)-(a_i-b_i)),嗯,因为(j)肯定比(i)大,所以枚举的时候就能存下来。从而可以拿到(60)分的好成绩。
C
题目的意思很简单啊,就是每一点的最短路的最后一条路径删去。考完试dalao们激情交流,枚举到倒数第二个点然后加上第二个点最短路啥啥的,但是仔细一想不对,因为有可能最短路就改变了。然后我暴力(n)遍(spfa)拿到30pts,他们只拿到了10pts。
//30pts code
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define LL long long
using namespace std;
struct edge {
int next,to,dis;
} e[400001];
int head[400001],tot,n,m,dis[100001],minn[100001],vis[100001],pre[100001];
void add(int x,int y,int z) {
e[++tot].next=head[x];
e[tot].to=y;
head[x]=tot;
e[tot].dis=z;
}
void spfa(int s) {
queue<int>q;
q.push(s);
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
vis[s]=1;
while(!q.empty()) {
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].next) {
int v=e[i].to;
if(dis[v]>dis[u]+e[i].dis) {
pre[v]=u;
dis[v]=dis[u]+e[i].dis;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
}
void spfa_(int s,int x,int y) {
queue<int>q;
q.push(s);
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
vis[s]=1;
while(!q.empty()) {
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].next) {
int v=e[i].to;
if((u==x && v==y) || (v==x && u==y))continue;
if(dis[v]>dis[u]+e[i].dis) {
dis[v]=dis[u]+e[i].dis;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
}
int main() {
// freopen("C.in","r",stdin);
// freopen("C.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=1; i<=n; i++)minn[i]=0x7fffffff;
spfa(1);
for(int i=2;i<=n;i++) {
spfa_(1,i,pre[i]);
minn[i]=min(minn[i],dis[i]);
}
for(int i=2; i<=n; i++) {
if(minn[i]!=1061109567)
printf("%d
",minn[i]);
else if(minn[i]==1061109567 || minn[i]==2147483647)printf("-1
");
}
return 0;
}
赛后看题解,真恶心人,最小路径树是什么鬼?are you sure noip kao ?
不学了
牛客网比赛
T1中位数
怎么说呢暴力本来是要跑60的啊,n方logn可是能过的啊,偏偏就30
//30pts
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 100001;
int n,len,maxn;
int a[N],b[N];
int main(){
scanf("%d%d",&n,&len);
for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}
for(int l=1,r=l+len-1;r<=n;l++,r++){
sort(b+l,b+1+r);
if(len%2==1)maxn=max(maxn,b[l+(len+1)/2-1]);
else maxn=max(maxn,b[l+len/2-1]);
for(int i=l;i<=r;i++)b[i]=a[i];
}
printf("%d",maxn);
}
然后第二档60pts主席树查找区间第k大,弃疗弃疗,直接看正解
正解是枚举一个可能值,然后会发现可以二分,然后在记录。
对于第三档数据,直接枚举最终可能的答案x。把数字(>)x的记为1, (le)x的记为-1。检验是否存在和$ge(0的区间。可以维护一个前缀和的前缀最大值。时间复杂度)O(Mn)$,
M为不同的数的个数。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
int a[maxn], n, L;
bool test(int z)
{
static int b[maxn];
for(int i = 1;i <= n;i ++)
if (a[i] >= z) b[i] = 1; else b[i] = -1;
for(int i = 1, mi = (1 << 30);i <= n;i ++)
{
if (i >= L) mi = min(mi, b[i - L]);
b[i] += b[i - 1];
if (i >= L && b[i] - mi > 0) return 1;
}
return 0;
}
int main()
{
freopen("median.in","r",stdin),freopen("median.out","w",stdout);
scanf("%d%d", &n, &L);
for(int i = 1;i <= n;i ++) scanf("%d", &a[i]);
int l = 0, r = int(1e9), tmp;
for(int mid;l <= r;)
{
mid = l + r >> 1;
if (test(mid)) tmp = mid, l = mid + 1; else r = mid - 1;
}
printf("%d
", tmp);
return 0;
}
数数字
唯一一道暴力分多的题目。。
30pts不用说了,直接上代码了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define LL long long
using namespace std;
LL l,r,l1,r1;
int f[50000001];
int ans;
int fw(int x) {
int z=1;
while(x) {
if(x<50000001)
if(f[x])return z*f[x];
z=z*(x%10);
x/=10;
}
if(x<50000001)
f[x]=z;
return z;
}
int main() {
// freopen("1.in","r",stdin);
f[1]=1;
f[2]=2;
f[3]=3;
f[4]=4;
f[5]=5;
f[6]=6;
f[7]=7;
f[8]=8;
f[9]=9;
f[10]=0;
for(int i=11; i<=1000000; i++)
f[i]=f[i/10]*(i%10);
scanf("%lld%lld%lld%lld",&l,&r,&l1,&r1);
for(int i=l; i<=r; i++)
if(fw(i)>=l1&&fw(i)<=r1)ans++;
printf("%d",ans);
}
就是恶心的打表记忆化加暴力。但还是40pts。
然后,正解就是数位(dp)。
先贴一下std压一下
//code - std
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int co[10][10];
ll l,r,l1,r1, pw[10][100];
inline int merge(int i, int cr, int b)
{
return (cr > b ? 2 : (cr == b ? i : 0));
}
ll calc_no_zero(ll x)
{
if (x <= 0) return 0;
static ll f[2][3][60][38][27][22];
memset(f,0,sizeof f);
int cr = 0;
f[0][1][0][0][0][0] = 1;
ll ans = 0, val;
for(;x;x /= 10, cr ^= 1)
{
int bit = x % 10;
memset(f[cr ^ 1], 0, sizeof f[cr]);
for(int i = 0;i < 3;i ++)
for(int n_2 = 0;n_2 < 60;n_2 ++)
for(int n_3 = 0;n_3 < 38;n_3 ++)
for(int n_5 = 0;n_5 < 27;n_5 ++)
for(int n_7 = 0;n_7 < 22;n_7 ++)
if (val = f[cr][i][n_2][n_3][n_5][n_7])
{
ll q1 = r1 / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
ll p1 = (l1 - 1) / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
if (q1 >= 1 && p1 <= 0) ans += val;
if (q1 <= 0) continue;
for(int p = 1;p < 10;p ++)
{
int ni = merge(i, p, bit);
int c_2 = n_2 + co[p][2];
int c_3 = n_3 + co[p][3];
int c_5 = n_5 + co[p][5];
int c_7 = n_7 + co[p][7];
if (c_2 >= 60 || c_3 >= 38 || c_5 >= 27 || c_7 >= 22) continue;
f[cr ^ 1][ni][c_2][c_3][c_5][c_7] += val;
}
}
}
for(int i = 0;i < 2;i ++)
for(int n_2 = 0;n_2 < 60;n_2 ++)
for(int n_3 = 0;n_3 < 38;n_3 ++)
for(int n_5 = 0;n_5 < 27;n_5 ++)
for(int n_7 = 0;n_7 < 22;n_7 ++)
if (val = f[cr][i][n_2][n_3][n_5][n_7])
{
ll q1 = r1 / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
ll p1 = (l1 - 1) / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
if (q1 >= 1 && p1 <= 0) ans += val;
}
return ans;
}
ll calc_with_zero(ll x)
{
if (l1) return 0;
static ll g[2][3][2][2];
memset(g,0,sizeof g);
int cr = 0;
ll ans = 0;
g[0][1][0][0] = 1;
for(;x;x /= 10, cr ^= 1)
{
int bit = x % 10;
memset(g[cr ^ 1], 0, sizeof g[cr]);
for(int i = 0;i < 3;i ++)
for(int j = 0;j < 2;j ++)
for(int hd = 0;hd < 2;hd ++)
if (g[cr][i][j][hd])
{
if (hd && j) ans += g[cr][i][j][hd];
for(int p = 0;p < 10;p ++)
{
int ni = merge(i, p, bit);
int nj = (j | (p == 0));
int hdt = (p > 0);
g[cr ^ 1][ni][nj][hdt] += g[cr][i][j][hd];
}
}
}
return ans + g[cr][0][1][1] + g[cr][1][1][1];
}
ll calc(long long x)
{
return calc_no_zero(x) + calc_with_zero(x);
}
int main()
{
freopen("count.in","r",stdin),freopen("count.out","w",stdout);
for(int i = 1;i < 10;i ++)
{
for(int j = 2;j < 10;j ++)
for(int k = i;k % j == 0;k /= j) co[i][j] ++;
pw[i][0] = 1;
for(int j = 1;j <= 60;j ++) pw[i][j] = pw[i][j - 1] * i;
}
cin >> l >> r >> l1 >> r1;
ll ans = 0;
if (!l) ans += (l1 == 0), ++ l;
if (l <= r) ans += calc(r) - calc(l - 1);
cout << ans << endl;
return 0;
}
正解可以使用记忆化搜索或者DP。这里介绍DP的做法。
由于数位乘积只是由0到9,可以把L1,R1分类讨论,假如区间包含0,则原来的数字分为包含至少一个零,和完全不包含0两类。前一类可以使用简单的数位DP计算。接下来介绍后一类。
由于只包含1到9,所以只用记录乘积的质因数分解中,出现了多少2, 3, 5, 7。题目中的L1,R1不超过10^18,可以计算出2最多为59个, 3最多为37, 5最多为26, 7最多为21。设F[i][c_2][c_3][c_5][c_7]表示考虑到第i位,乘积状态为c_2,c_3,c_5,c_7的数位DP情况。接着就是经典数位DP做法。空间可能比较紧,需要用滚动数组
保护
考试想了一个树剖的方法,但是临时改题面了,(...)好端端的100跑了。改完题面什么破题, are you sure noip tigaozu?
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005;
struct node
{
int l,r,cnt;
}T[maxn * 100];
vector<int> lk[maxn];
int fa[maxn][20],dep[maxn],ord[maxn],cnt,n,m,ans[maxn];
void dfs(int now,int pre)
{
dep[now] = dep[pre] + 1;
fa[now][0] = pre;
for(int i = 1;(1 << i) <= dep[now];i ++) fa[now][i] = fa[fa[now][i - 1]][i - 1];
for(int i = 0;i < lk[now].size();i ++)
if (lk[now][i] != pre)
dfs(lk[now][i], now);
}
void insert(int l,int r,int p,int &jd)
{
if (!jd) jd = ++ cnt;
T[jd].cnt ++;
if (l == r) return;
int mid = l + r >> 1;
if (p <= mid) insert(l,mid,p,T[jd].l); else insert(mid + 1,r,p, T[jd].r);
}
int merge(int l,int r,int a,int b)
{
if ((!a) || (!b)) return a + b;
int jd = ++ cnt;
T[jd].cnt = T[a].cnt + T[b].cnt;
if (l == r) return jd;
int mid = l + r >> 1;
T[jd].l = merge(l,mid,T[a].l,T[b].l);
T[jd].r = merge(mid + 1,r,T[a].r, T[b].r);
return jd;
}
int find_k(int l,int r,int k,int jd)
{
if (T[jd].cnt < k) return (1 << 30);
if (l == r) return l;
int mid = l + r >> 1;
if (T[T[jd].l].cnt >= k) return find_k(l,mid,k,T[jd].l);
return find_k(mid + 1,r,k - T[T[jd].l].cnt, T[jd].r);
}
void dfs_w(int now,int pre)
{
for(int i = 0;i < lk[now].size();i ++)
if (lk[now][i] != pre)
{
dfs_w(lk[now][i], now);
ord[now] = merge(1,n,ord[now], ord[lk[now][i]]);
}
}
int get_lca(int u,int v)
{
if (dep[u] > dep[v]) swap(u,v);
for(int i = 18;i + 1;i --)
if (dep[fa[v][i]] >= dep[u]) v = fa[v][i];
if (u == v) return u;
for(int i = 18;i + 1;i --)
if (fa[v][i] != fa[u][i]) v = fa[v][i], u = fa[u][i];
return fa[u][0];
}
int main()
{
freopen("guard.in","r",stdin),freopen("guard.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i = 1,u,v;i < n;i ++)
{
scanf("%d%d", &u, &v);
lk[u].push_back(v), lk[v].push_back(u);
}
dfs(1,0);
for(int i = 1,u,v;i <= m;i ++)
{
scanf("%d%d", &u, &v);
int p = get_lca(u,v);
insert(1,n,dep[p],ord[u]),insert(1,n,dep[p],ord[v]);
}
dfs_w(1,0);
int q;
scanf("%d", &q);
for(int i = 1;i <= q;i ++)
{
int u,k;
scanf("%d%d", &u, &k);
printf("%d
", max(0, dep[u] - find_k(1,n,k,ord[u])));
}
return 0;
}
题解也看不懂,挂惨。