2025-09-21 05:38:01ICPC 2019 徐州网络赛
ICPC 2019 徐州网络赛
比赛时间:2019.9.7
比赛链接:The Preliminary Contest for ICPC Asia Xuzhou 2019
赛后的经验总结
// 比赛完才反应过来没看完全部题, J题树形dp没写太亏了。。。
// 几何旋律AK了,太强了Orz
// 虽然我们队A了8题,但如果时间分配好,认真读题避免看错题的话,应该至少多A一题
A. Who is better?
题意
两个人玩游戏,谁先消灭完敌人就赢。两条规则:1. 第一个人不能全部消灭完; 2. 后一次消灭的人数不超过前一次的两倍。
敌人人数通过 n 个同余方程求出。
非常明显的斐波那契博弈 + 中国剩余定理。
坑点
很裸的两个知识点强行结合在一起,于是愉快地抄起板子来,一交即WA,再交继续WA...
考虑到 CRT 计算过程中 ll 会溢出,改写 __int128,继续WA...
最后想起洛谷上测板子时候,把合并过程中的 a[i] >= m[i] 条件注释掉,然后就A了。。。
也就是说,测试数据认为余数比模数大也是合法的。
AC代码(新的 CRT 加强模板)
#include
#include
#include
using namespace std;
typedef long long ll;
typedef __int128 i128;
const ll maxn = 1e15;
i128 exgcd(i128 a, i128 b, i128& x, i128& y) {
if(b==0) {
x = 1; y = 0;
return a;
}
i128 d = exgcd(b, a%b, y, x);
y -= (a/b) * x;
return d;
}
i128 a[20], m[20];
int n;
i128 excrt() {
if (n==1) {
if (m[0]>a[0]) return a[0];
else return -1;
}
i128 x, y, d;
for (int i=1;i d = exgcd(m[0], m[i], x, y); if ((a[i]-a[0])%d!=0) return -1; i128 t = m[i] / d; x = ((a[i]-a[0]) / d * x % t + t) % t; a[0] = x * m[0] + a[0]; m[0] = m[0] * m[i] / d; a[0] = (a[0]%m[0] + m[0]) % m[0]; } return a[0]; } ll fib[100]; int main() { cin>>n; for(int i=0;i ll a1, a2; cin>>a1>>a2; m[i] = a1, a[i] = a2; } i128 x = excrt(); if(x<=1 || x>maxn) { return 0 * printf("Tankernb!\n"); } set fib[1] = 1; for(int i=2;i<100;i++) { fib[i] = fib[i-1]+fib[i-2]; if(fib[i]>maxn) break; S.insert(fib[i]); } if(S.find(x)==S.end()) printf("Zgxnb!\n"); else printf("Lbnb!\n"); } B. so easy 题意 有 n 个点从 1~n 编号,q 次操作 (z, x) 。(\(1 \le x < n <10^9\), \(1 \le q<10^6\)) z = 1 时,删除点 x;z = 2 时,查询从 x 之后能到达的第一个点(包括 x )。 思路 n 的范围太大,线段树什么都存不了。 卡了很长时间,一直段错误。最后队友Yu用对查询离散化+并查集A了。 题解代码(使用 unordered_map) #include #include #include using namespace std; const int maxn = 100010; unordered_map int findfa(int x) { if(!fa.count(x)) return x; return fa[x] = findfa(fa[x]); } int main() { int n, q; scanf("%d %d", &n, &q); int op, x; while(q--) { scanf("%d %d", &op, &x); if(op==1) { fa[x] = findfa(x+1); } else { int ans = findfa(x); if(ans>n) ans = -1; printf("%d\n", ans); } } return 0; } C. Buy Watermelon 题意 出题人的sb英语,三个人读完题都不知道写的什么。签到题WA了三次给强行怼过了。 大概题目想表达的是,要把西瓜分成重量都为偶数克的两部分吧。 思路 西瓜重量 w 必须为大于2的偶数。 AC代码 略。 D. Carneginon 队友Wu写的,据说是简单的KMP,也是签到题。 AC代码 #include using namespace std; const int maxn=1e5+10; char s[maxn],t[maxn]; int nexts[maxn],nextt[maxn]; void getnext_t(char *ptr){ int i,n,k; n=strlen(ptr); k=-1; nextt[0]=-1; i=0; while(i { if(k==-1 || ptr[i]==ptr[k]) { i++; k++; nextt[i]=k; } else k=nextt[k]; } } void getnext_s(char *ptr){ int i,n,k; n=strlen(ptr); k=-1; nexts[0]=-1; i=0; while(i { if(k==-1 || ptr[i]==ptr[k]) { i++; k++; nexts[i]=k; } else k=nexts[k]; } } int kmps(char *a,char *b)//匹配ab两串,a为父串 { int i=0,j=0; int len1=strlen(a); int len2=strlen(b); getnext_t(t); while(i { if(j==-1||a[i]==b[j]) ++i,++j; else j=nextt[j]; } if(j==len2) return i-j; else return -1; } int kmpt(char *a,char *b)//匹配ab两串,t为父串 { int i=0,j=0; int len1=strlen(a); int len2=strlen(b); while(i { if(j==-1||a[i]==b[j]) ++i,++j; else j=nexts[j]; } if(j==len2) return i-j; else return -1; } bool cmp(char *a, char *b){ int len=strlen(s); for(int i=0;i if(a[i]!=b[i]) return 0; }return 1; } int main() { scanf("%s",s); getnext_s(s); int q;scanf("%d",&q); for(int i=1;i<=q;i++){ scanf("%s",t); int ls=strlen(s),lt=strlen(t); if(ls==lt){ if(cmp(s,t)) printf("jntm!\n"); else printf("friend!\n"); }else if(ls>lt){ if(kmps(s,t)!=-1) printf("my child!\n"); else printf("oh, child!\n"); }else { if(kmpt(t,s)!=-1) printf("my teacher!\n"); else printf("senior!\n"); } } return 0; } E. XKC's basketball team 题意 定义 w[i] 的 angry 值 为 w[i] 与右边最远的满足 w[j] >= w[i] + m 之间的人数。 求所有点的 angry 值之和。 思路 队友Yu写的,当时我沉浸在数论题中。。。 AC代码 #include #include #include #include #define inf 1000000000 using namespace std; const int N = 5e5+10; int tree[N<<4],num[N],cnt; int ls[N<<4],rs[N<<4]; void update(int l,int r,int pos,int x,int d) { if(l==r) { tree[pos] = max(tree[pos],d); return; } int mid = (l+r)>>1; if(x<=mid) { if(ls[pos]==0) ls[pos] = ++cnt; update(l,mid,ls[pos],x,d); } else { if(rs[pos]==0) rs[pos] = ++cnt; update(mid+1,r,rs[pos],x,d); } tree[pos]=max(tree[ls[pos]],tree[rs[pos]]); } int qry(int l,int r,int L,int R,int pos) { if(L>R)return 0; if(pos==0)return 0; if(L<=l && r<=R) return tree[pos]; int mid = (l+r)>>1; int ret = 0; if(L<=mid) ret = max(ret,qry(l,mid,L,R,ls[pos])); if(mid ret = max(ret,qry(mid+1,r,L,R,rs[pos])); return ret; } int arr[N],hx[N]; int head; int main() { cnt = 0; int n,m;scanf("%d%d",&n,&m); head = ++cnt; for(int i=1;i<=n;++i) { scanf("%d",&arr[i]); hx[i] = arr[i]; //update(1,inf,head,arr[i],i); } sort(hx+1,hx+1+n); int len = unique(hx+1,hx+1+n)-hx-1; for(int i=1;i<=n;++i) { int pos = lower_bound(hx+1,hx+1+len,arr[i])-hx; update(1,len,head,pos,i); } bool ok = 1; for(int i=1;i<=n;++i) { if(ok==0) printf(" "); ok=0; int rs = arr[i]+m; if(rs>hx[len]) printf("-1"); else { int pos = lower_bound(hx+1,hx+1+len,rs)-hx; //cout< int ans = qry(1,len,pos,len,head); if(ans<=i) printf("-1"); else printf("%d",ans-i-1); } } puts(""); return 0; } G. Colorful String 题意 给定一个字符串,定义 value 为回文子串的不同字母个数,求它的全部回文子串的 value 之和。 思路 应该是字符串水题。 我用马拉车调了半天没弄出来。字符串队友那边用回文自动机直接一发A了。 AC代码 #include using namespace std; typedef long long ll; const int maxn=3e5+5; const int N=26; struct Palindromic_Tree { int next[maxn][N]; int fail[maxn]; int cnt[maxn];//第i个节点表示的回文串出现的次数,最后要调用count函数完成计算 int val[maxn]; int len[maxn];//节点i表示的回文串的长度 int S[maxn];//存放添加的字符 bool used[26]; int last; int n;//字符数组指针 int p;//节点指针 int newnode(int l) { memset(next[p],0,sizeof next[p]); cnt[p]=0; len[p]=l; return p++; } void init() { p=0; newnode(0); newnode(-1); memset(used,0,sizeof used); last=0; n=0; S[n]=-1; fail[0]=1; } int get_fail(int x) { while(S[n-len[x]-1]!=S[n]) x=fail[x]; return x; } void add(int c) { c-='a'; S[++n]=c; int cur=get_fail(last); if(!next[cur][c]) { int now=newnode(len[cur]+2); fail[now]=next[get_fail(fail[cur])][c]; next[cur][c]=now; } last=next[cur][c]; cnt[last]++; } void count() { for(int i=p-1;i>=0;i--) cnt[fail[i]]+=cnt[i]; } void dfs(int now,ll num){ for(int i=0;i<26;i++) { if(next[now][i]){ if(!used[i]) { used[i]=1; dfs(next[now][i],num+1); used[i]=0; }else dfs(next[now][i],num); } } val[now]=num; } }pam; char s[maxn]; int main() { scanf("%s",s); pam.init(); int len=strlen(s); for(int i=0;i pam.count(); pam.dfs(0,0); pam.dfs(1,0); ll ans=0; for(int i=2;i printf("%lld\n",ans); return 0; } J. Random Access Iterator (补) 题意 在树上按照图中 dfs 的方式,每次随机选一个儿子往下 dfs 得到树的深度。 求程序返回树的正确深度的概率。 思路 树形dp 比赛时候没有写,我的锅,全耗在数论题上了,结果全是通过数不超过100的难题。 注意题目程序的 dfs 每个节点跑了 k 次,相当于独立重复实验。 设tmp为节点 u 能达到根节点的概率, $tmp = \sum {1 \over k} dp[v] \( \)dp[u] = 1 - (1-tmp)^k$ AC代码 #include #include #include #include #include using namespace std; const int mod = 1e9+7; const int maxn = 1000100; typedef long long ll; vector ll dp[maxn], ans; int dep[maxn], maxDep; ll powmod(ll a, ll n) { ll res = 1; while(n) { if(n&1) res = res * a % mod; a = a * a % mod; n >>= 1; } return res; } void getDep(int u, int fa, int d) { dep[u] = d; maxDep = max(d, maxDep); for(int i=0;i int v = G[u][i]; if(v!=fa) { getDep(v, u, d+1); } } } void dfs(int u, int fa) { if(dp[u]!=-1) return; ll tmp = 0; int son = G[u].size(); if(fa!=-1) --son; for(int i=0;i int v = G[u][i]; if(v==fa) continue; dfs(v, u); tmp = (tmp + dp[v]*powmod(son, mod-2)%mod ) % mod; } dp[u] = (1-powmod((1-tmp+mod)%mod, son) + mod) % mod; } int main() { int n; scanf("%d", &n); for(int i=0;i int u, v; scanf("%d %d", &u, &v); G[u].push_back(v); G[v].push_back(u); } getDep(1, -1, 1); for(int i=1;i<=n;i++) { if(dep[i]==maxDep) dp[i] = 1; else { if(G[i].size()==1) dp[i] = 0; else dp[i] = -1; } // cout< } dp[1] = -1; dfs(1, -1); printf("%lld\n", dp[1]); return 0; } K. Center 题意 给定平面上 n 个点,求加入最少的点使全部点能够中心对称。 思路 时限2s,复杂度O(n^3*logn)的暴力枚举算法勉强能过。 直接写完交一发,果然T了。 改用unordered_map存两点映射后的整数,降低了 logn 的复杂度,还是T了。 发现固定中心点的时候,剩下的点的对称点不会发生重合,直接检查是否插入新的点,带上 if(now>ans) break; 优化check过程,最后A了,几分钟之和才拿下A题。 AC代码(赛后改回了set可以A) #include #include #include #include #include using namespace std; const int maxn = 100010; typedef pair struct Point { int x, y; Point(int xx=0, int yy=0):x(xx), y(yy) {} }p[1010]; set int main() { int n; scanf("%d", &n); for(int i=0;i scanf("%d %d", &p[i].x, &p[i].y); S.insert(make_pair(p[i].x, p[i].y)); } int ans = n; for(int i=0;i for(int j=i+1;j int now = 0; for(int k=0;k if(S.find(make_pair(p[i].x+p[j].x-p[k].x, p[i].y+p[j].y-p[k].y))==S.end()) ++now; if(now>ans) break; // 没有会TLE } ans = min(ans, now); } } for(int i=0;i int now = 0; for(int j=0;j if(i==j) continue; if(S.find(make_pair(2*p[i].x-p[j].x, 2*p[i].y-p[j].y))==S.end()) ++now; if(now>ans) break; } ans = min(ans, now); } printf("%d\n", ans); return 0; } M. Longest subsequence 题意 给定两个字符串 s, t ,求 s 串中满足字典序大于 t 串的最长子序列长度。 思路 又是字符串,大概dp吧。 Wu读错题,让Yu白搞了快一个小时。。。 注意是子序列不是子串啊!!! AC代码 #include #include #include using namespace std; const int N = 1e6+10; char str[N]; int dp[N][30]; char ptn[N]; int main() { int n,m;scanf("%d%d",&n,&m); scanf("%s%s",str+1,ptn+1); m++; ptn[m]='a'-1; for(int i=0;i<26;++i) dp[n][i]=n*2; for(int i=n-1;i>=0;i--) { for(int j=0;j<26;++j) dp[i][j] = dp[i+1][j]; dp[i][str[i+1]-'a'] = i+1; } int ans = 0; int now = 0; for(int i=1;i<=m && now<=n;++i) { for(int j=ptn[i]-'a'+1;j<26;++j) if(dp[now][j]<=n) ans = max(ans,n-dp[now][j]+i); now = dp[now][ptn[i]-'a']; //cout<
} if(ans) cout< else puts("-1"); return 0; } (未完待补题。。。)