2019安徽省赛题解

A. 机器人足球

模拟

#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
 
double dis(int x1, int y1, int x2, int y2) {
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
 
int main() {
    int x, y;
    scanf("%d%d", &x, &y);
    double d = dis(x,y,100,10)-10;
    printf("%.3lf\n",max(d,0.));
}

 

 

B.纸牌识别

模拟 

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
typedef pair<int,int> pii;
 
char x;
int y;
int a[200];
set<pii> s;
 
int main() {
    a['P']=a['K']=a['H']=a['T']=13;
    while (~scanf(" %c%d", &x, &y)) {
        if (s.count(pii(x,y))) return puts("GRESKA"),0;
        s.insert(pii(x,y));
        --a[x];
    }
    printf("%d %d %d %d\n",a['P'],a['K'],a['H'],a['T']);
}

 

 

C.卡牌对决

贪心, 前$\frac{n}{2}$轮尽量取最大, 后$\frac{n}{2}$轮尽量取最小.

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 1e5+10;
int n, a[N];
set<int> Bob, Alice;
 
int main() {
    scanf("%d", &n);
    REP(i,1,n) {
        scanf("%d",a+i);
        Bob.insert(a[i]);
    }
    REP(i,1,2*n) if (!Bob.count(i)) Alice.insert(i);
    int ans = 0;
    sort(a+1,a+1+n/2,greater<int>());
    REP(i,1,n/2) {
        int x = *(--Alice.end());
        if (x>a[i]) ++ans, Alice.erase(x);
    }
    sort(a+1+n/2,a+1+n);
    REP(i,n/2+1,n) {
        int x = *Alice.begin();
        if (x<a[i]) ++ans, Alice.erase(x);
    }
    printf("%d\n", ans);
}

 

 

D.自驾游

先跑$2$次$dijkstra$求出$N$到每个点最短路, 再建图跑一次$dijkstra$求出$1$到$N$的最短路即为最少花费.

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
typedef long long ll;
 
const int N = 5e4+10;
int n, m;
struct _ {
    int to,w;
    _ (int to=0,int w=0) :to(to),w(w) {}
};
vector<_> g1[N], g2[N], g3[N];
ll d1[N], d2[N], d3[N];
int vis[N], u[N], v[N], p[N], q[N];
struct node {
    int id;
    ll w;
    node (int id=0, ll w=0) :id(id),w(w) {}
    bool operator < (const node &rhs) const {
        return w>rhs.w;
    }
};
priority_queue<node> pq;
 
void dij(vector<_> g[], ll d[], int s) {
    memset(d,0x3f,sizeof d1);
    memset(vis,0,sizeof vis);
    pq.push(node(s,d[s]=0));
    while (pq.size()) {
        int u = pq.top().id; pq.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i=0; i<g[u].size(); ++i) {
            _ e = g[u][i];
            ll dd = e.w+d[u];
            if (dd<d[e.to]) pq.push(node(e.to,d[e.to]=dd));
        }
    }
}
 
int main() {
    scanf("%d%d", &n, &m);
    REP(i,1,m) {
        scanf("%d%d%d%d", u+i, v+i, p+i, q+i);
        g1[v[i]].pb(_(u[i],p[i]));
        g2[v[i]].pb(_(u[i],q[i]));
 
    }
    dij(g1,d1,n);
    dij(g2,d2,n);
    REP(i,1,m) {
        int c = 0;
        if (d1[v[i]]+p[i]>d1[u[i]]) ++c;
        if (d2[v[i]]+q[i]>d2[u[i]]) ++c;
        g3[u[i]].pb(_(v[i],c));
    }
    dij(g3,d3,1);
    printf("%lld\n", d3[n]);
}

 

 

E.现代艺术

对称轴有两种情况, (1)点$1$与其他点的中垂线. (2)过点$1$.

情况(1)直接枚举, 对于情况(2)我们再考虑点$2$的位置, $2$要么也在对称轴上, 否则对称轴一定在$2$与其他点连线的中垂线上, 再对点$2$判断一次即可.

 

 

F.割草

刚开始以为每次修改一个连通块会最优, 但下面这组数据最优情况是修改$(1,1),(2,2),(5,1)$最优解为$61$, 若删连通块的话最优只能取到$63$

5 4 5 7
#...
.#..
.###
.###
#...

然后说正解, $S$连低点, 容量为$A$, 高点连$T$容量为$A$, 每个点$(i,j)$再向$(i+1,j)$和$(i,j+1)$连边, 若同为高点或低点, 连双向边, 容量为$B$, 否则低点向高点连边, 容量为$B$. 跑最小割即为答案.

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

const int N = 1e6+10, INF = 0x3f3f3f3f;
int n, m, a, b, S, T;
char s[55][55];
struct edge {
    int v,next;
    ll w;
    edge () {}
    edge (int v,int next,ll w) :v(v),w(w),next(next) {}
} e[N];
int head[N], dep[N], vis[N], cur[N], cnt=1;
queue<int> Q;
pii get(int t) {
	int x = (t-1)/m+1, y = (t-1)%m+1;
	return pii(x,y);
}
void add(int u, int v, ll w) {
    e[++cnt] = edge(v,head[u],w);
    head[u] = cnt;
    e[++cnt] = edge(u,head[v],0);
    head[v] = cnt;
}
int bfs() {
    REP(i,1,T) dep[i]=INF,vis[i]=0,cur[i]=head[i];
    dep[S]=0,Q.push(S);
    while (Q.size()) {
        int u = Q.front(); Q.pop();
        for (int i=head[u]; i; i=e[i].next) {
            if (dep[e[i].v]>dep[u]+1&&e[i].w) {
                dep[e[i].v]=dep[u]+1;
                Q.push(e[i].v);
            }
        }
    }
    return dep[T]!=INF;
}
  
ll dfs(int x, ll w) {
    if (x==T) return w;
    ll used = 0;
    for (int i=cur[x]; i; i=e[i].next) {
        cur[x] = i;
        if (dep[e[i].v]==dep[x]+1&&e[i].w) {
            int flow = dfs(e[i].v,min(w-used,e[i].w));
            if (flow) {
                used += flow;
                e[i].w -= flow;
                e[i^1].w += flow;
                if (used==w) break;
            }
        }
    }
    return used;
}
ll dinic() {
    ll ans = 0;
    while (bfs()) ans+=dfs(S,1e18);
    return ans;
}
int id(int x, int y) {
    return (x-1)*m+y;
}
int main() {
	scanf("%d%d%d%d", &n, &m, &a, &b);
	REP(i,1,n) scanf("%s",s[i]+1);
	S = n*m+1, T = S+1;
	REP(i,1,n) REP(j,1,m) {
		if (s[i][j]=='.') add(S,id(i,j),b);
		else add(id(i,j),T,b);
		if (i!=n) {
			if (s[i][j]=='.'&&s[i+1][j]=='#') add(id(i,j),id(i+1,j),a);
			else add(id(i+1,j),id(i,j),a);
			if (s[i][j]==s[i+1][j]) add(id(i,j),id(i+1,j),a);
		}
		if (j!=m) {
			if (s[i][j]=='.'&&s[i][j+1]=='#') add(id(i,j),id(i,j+1),a);
			else add(id(i,j+1),id(i,j),a);
			if (s[i][j]==s[i][j+1]) add(id(i,j),id(i,j+1),a);
		}
	}
	printf("%lld\n", dinic());
}

 

 

 

  

G.括号序列

$dp$求出长为$x$, 左括号比右括号多$y$个时的方案数. 然后从前往后枚举, 若放'('的方案数不少于$k$则放'(', 否则放')'.

注意方案数是指数级, 会爆long long. 

#include <iostream>
#define REP(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
 
const int N = 2010;
int n;
ll f[N][N], k;
char ans[N];
 
int main() {
    scanf("%d%lld", &n, &k);
    f[0][0] = 1;
    ll INF = 1e18+10;
    REP(i,1,n) REP(j,0,n) if (f[i-1][j]) {
        f[i][j+1] += f[i-1][j];
        if (j) f[i][j-1] += f[i-1][j];
        f[i][j+1] = min(f[i][j+1], INF);
        if (j) f[i][j-1] = min(f[i][j-1], INF);
    }
    int now = 0;
    REP(i,1,n) {
        if (f[n-i][now+1]>=k) ans[i]='(',++now;
        else ans[i]=')',k-=f[n-i][now+1],--now;
    }
    puts(ans+1);
}

 

 

 

 

H. 不要回文

大回文一定包含小回文, 只用判断长为2和3的回文即可.

#include <iostream>
#include <cstdio>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
 
const int N = 1e6+10;
int n, a[N];
char s[N];
 
int main() {
    scanf("%s", s+1);
    n = strlen(s+1);
    REP(i,1,n) a[i]=s[i];
    int ans = 0, cur = 0;
    REP(i,1,n) {
        if (a[i]==a[i-1]) ++ans,a[i]=--cur;
        if (i>=2&&a[i]==a[i-2]) ++ans,a[i]=--cur;
    }
    printf("%d\n", ans);
}

 

I. 你的名字

判断一个串是否是另一个串的子序列, 二分$O(nlogn)$, 或者序列自动机$O(n\Sigma)$.

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <vector>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
 
const int N = 1e3+10;
int n, m;
char s[N][N], p[N][N];
vector<int> g[N];
 
int chk(char *p) {
    int n = strlen(p), now = 0;
    REP(i,0,n-1) {
        vector<int>::iterator t = lower_bound(g[p[i]].begin(),g[p[i]].end(),now);
        if (t==g[p[i]].end()) return 0;
        now = *t+1;
    }
    return 1;
}
void trans(char *s) {
    int n = strlen(s);
    REP(i,0,n-1) if (islower(s[i])) s[i]=s[i]-'a'+'A';
}
 
int main() {
    scanf("%d%d", &n, &m);
    REP(i,1,n) scanf("%s",s[i]),trans(s[i]);
    REP(i,1,m) scanf("%s",p[i]),trans(p[i]);
    REP(i,1,n) {
        int ans = 0, n = strlen(s[i]);
        REP(j,0,n-1) g[s[i][j]].pb(j);
        REP(j,1,m) ans += chk(p[j]);
        REP(j,0,n-1) g[s[i][j]].clear();
        printf("%d\n", ans);
    }
}

 

J.密信

$dp[i][j]$为前$i$位匹配$j$位的方案数, 直接算是$O(|p|n)$, 矩阵优化到$O(|p|^3logn)$.

#include <iostream>
#include <cstdio>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
 
const int N = 55;
ll n, M;
int len, f[N];
char s[N];
 
void getFail() {
    int j = 0;
    REP(i,1,len-1) {
        while (j&&s[j]!=s[i]) j=f[j];
        if (s[j]==s[i]) ++j;
        f[i+1] = j;
    }
}

ull qmul(const ull a, const ull b, const ull md) {
	ll c=(ll)a*b-(ll)((ull)((long double)a*b/md)*md);
	return c<0?md+c:((ull)c>md?c-md:c);
}

struct Mat {
    ll v[55][55];
    Mat() {memset(v, 0, sizeof v);}
    Mat operator * (const Mat& b) const {
        Mat c;
        REP(k,0,len)REP(i,0,len)REP(j,0,len) {
            c.v[i][j] = (qmul(v[i][k],b.v[k][j],M)+c.v[i][j])%M;
        }
        return c;
    }
    Mat operator ^ (ll nn) {
        Mat b, a=*this;
        REP(i,0,len) b.v[i][i]=1;
        while(nn) {
            if(nn&1LL) b=b*a;
            nn>>=1LL,a=a*a;
        }
        return b;
    }
};
 
 
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%lld%lld%s", &n, &M, s);
        len = strlen(s);
        getFail();
        Mat g;
        REP(i,0,len) REP(k,'a','z') {
            int nxt = i;
            while (nxt&&s[nxt]!=k) nxt = f[nxt];
            if (s[nxt]==k) ++nxt;
            if (i==len) nxt = len;
            ++g.v[nxt][i];
        }
        g = g^n;
        printf("%lld\n", g.v[len][0]);
    }
}

 

 

 

 

K.福报

点$i$的贡献为$i$的子树内$r_j<r_i$的$t_j$的和.

比赛的时候是用线段树合并写的, 实际上预处理出$dfs$序后, 贡献明显是个二维数点, 按$r$排序后, 树状数组统计一下即可, 这样时空的常数都会小很多. 

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
typedef long long ll;
 
const int N = 1e6+10;
int n, clk, rt;
int fa[N], L[N], R[N];
ll c[N], ans[N];
vector<int> g[N];
struct _ {int r,t,id;} a[N];
bool cmp(_ a, _ b) {
    return a.r<b.r;
}
void dfs(int x) {
    L[x] = ++clk;
    for (int i=0; i<g[x].size(); ++i) dfs(g[x][i]);
    R[x] = clk;
}
void add(int x, int v) {
    for (; x<=n; x+=x&-x) c[x]+=v;
}
ll qry(int x) {
    ll r = 0;
    for (; x; x^=x&-x) r+=c[x];
    return r;
}
int main() {
    scanf("%d", &n);
    REP(i,1,n) {
        scanf("%d%d%d",fa+i,&a[i].r,&a[i].t);
        a[i].id = i;
        if (fa[i]==-1) rt = i;
        else g[fa[i]].pb(i);
    }
    dfs(rt);
    sort(a+1,a+1+n,cmp);
    REP(i,1,n) {
        int j = i;
        while (a[j+1].r==a[i].r) ++j;
        REP(k,i,j) ans[a[k].id] = qry(R[a[k].id])-qry(L[a[k].id]-1);
        REP(k,i,j) add(L[a[k].id],a[k].t);
        i = j;
    }
    REP(i,1,n) printf("%lld\n", ans[i]);
}

 

L. 曲奇工厂

暴力枚举所有情况, 复杂度$O(n!2^n)$.

#include <iostream>
#include <cstdio>
#include <algorithm>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
 
const int N = 10;
int n, c, s, cnt;
int f[N];
struct {int x,y;} a[N], b[N], d[N];
int ans;
 
int calc(int n) {
    int sum = 0, v = s, ans = 0;
    REP(i,1,n) {
        if (sum<d[i].x) {
            int t = (d[i].x-sum+v-1)/v;
            sum += t*v;
            ans += t;
        }
        sum -= d[i].x;
        v += d[i].y;
    }
    if (sum<c) ans += (c-sum+v-1)/v;
    return ans;
}
 
void dfs(int now) {
    if (now>n) {
        REP(i,1,cnt) f[i] = i;
        do {
            REP(i,1,cnt) d[i]=b[f[i]];
            ans = min(ans, calc(cnt));
        } while (next_permutation(f+1,f+1+cnt));
    }
    else {
        dfs(now+1);
        b[++cnt] = a[now];
        dfs(now+1);
        --cnt;
    }
}
int main() {
    scanf("%d%d%d", &n, &c, &s);
    REP(i,1,n) scanf("%d%d",&a[i].x,&a[i].y);
    ans = (c+s-1)/s;
    dfs(1);
    printf("%d\n", ans);
}

 

转载于:https://www.cnblogs.com/uid001/p/10985618.html

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值