A. Approach(计算几何:三分)
记录:赛后补题
思路:三分
- 模拟过程,分两段,先是三分从A点走到B点(要使得前面的路径更短)。然后再站定在A点,三分另一个人继续走。
- 其实第一段可以直接判断(1)是否交叉=0(2)只考虑两个起点的连线与两个终点的连线。但由于这样也符合为一个抛物线。
- 为减少讨论,用三分处理。注意精度问题。
#include<bits/stdc++.h>
using namespace std;
double ax,ay,bx,by,cx,cy,dx,dy,len1,len2;
const double eps = 1e-12; // 精度
double get_dis(double x1,double y1,double x2,double y2){return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);}
double check(double dis){
// 计算长边上的点距离起点走了dis,两点的距离
double x1,y1,x2,y2; // 两点的坐标
if(dis>=len1-eps) x1=bx,y1=by;
else x1 = ax+(bx-ax)/len1*dis,y1 = ay+(by-ay)/len1*dis;
if(dis>=len2-eps) x2=dx,y2=dy;
else x2 = cx+(dx-cx)/len2*dis,y2 = cy+(dy-cy)/len2*dis;
return get_dis(x1,y1,x2,y2);
}
// 三分板子
double three_div(double left,double right){
// left and right represent the distance gone
double ans=1e25;
for(int i=1;i<=1e5;++i){
double lmid = (2*left+right)/3;
double rmid = (2*right+left)/3;
double resl = check(lmid),resr = check(rmid);
if(resl+eps<=resr) right = rmid;
else left = lmid;
ans = min({ans,resl,resr});
}
return ans;
}
int main(){
scanf("%lf%lf%lf%lf",&ax,&ay,&bx,&by);
scanf("%lf%lf%lf%lf",&cx,&cy,&dx,&dy);
len1 = sqrt(get_dis(ax,ay,bx,by)),len2 = sqrt(get_dis(cx,cy,dx,dy));
double ans = min(three_div(0,min(len1,len2)),three_div(min(len1,len2),max(len1,len2)));
printf("%.12lf\n",sqrt(ans));
}
B. Baby name(字符串:最大字典序子串)
记录:赛后补题
题意:在两个串中分别取子串,拼凑在一起构成新串。要使得这个新串的字典序最大。
思路:分别找到两个串中的字典序最大子串。再试图将他们两个拼凑在一起。
难点:如果暴力找字典序最大子串,复杂度会很高,因此需要一点剪枝操作:即发现,如果当前字母等于出现过的最大字母,而当前上一个字母也等于这个字母。那当前位置必不可能是最大子串的起始位置。
注:这道题如果用char型数组代替string,运行时间可以减半。
其实,这个思路是卡过去的。ababa这样的序列会
O
(
n
2
)
O(n^2)
O(n2)。
正解是SA后缀排序。
#include <bits/stdc++.h>
using namespace std;
int get_pos_of_min_string(string s,int len){
int pos=-1,mx=0;
for(int i=0;i<len;++i){
if(s[i]>mx) mx=s[i],pos=i;
else if(s[i]==mx&&s[i-1]!=mx){
for(int j=1;j+i<len;++j){
if(s[pos+j]>s[i+j]) break;
if(s[pos+j]<s[i+j]){
mx=s[i];
pos=i;
break;
}
}
}
}
return pos;
}
int main() {
string s1,s2;
cin>>s1>>s2;
int len1=s1.length(),len2=s2.length();
char max1=0,max2=0;
int pos1 = get_pos_of_min_string(s1,len1),pos2 = get_pos_of_min_string(s2,len2);
printf("%c",s1[pos1]);
for(int i=pos1+1;i<len1;++i){
if(s1[i]<s2[pos2]) break;
printf("%c",s1[i]);
}
for(int i=pos2;i<len2;++i) printf("%c",s2[i]);
puts("");
}
D. Dice(数学:矩阵快速幂+组合数学)
记录:赛后补题
- 考点:数学 组合数学 + 矩阵快速幂
- 题意:有n个一样的色子,每个色子的面的数目一样,给出一个数字m。
- m的含义:每个色子等于m的倍数的面被甩到的概率为0.
- 求:所有色子甩出的总和等于m的倍数的概率。
- 思路:容易想到题目只关乎是否是m的倍数,我们把所有面的序号都取模。
- 得到数组num,记录每个色子中,面的序号取模后等于i的面的个数。
- 这个概率的分母很容易:即每个色子除却m的倍数(num的i==0)的面之外的其他的面的总数,取n次方。
- 假若现在只有两个色子,他们的和==0的情况个数newnum[0]=\sum{num[i]*num[m-i]}+num[0]*num[0]
- 把两个色子得到的和的结果看成一个新的不同的色子。继续将这个色子与第三个色子进行求和。
- 于是我们想到矩阵快速幂.
- 矩阵具体的构造:参考大佬博客
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 204;
const int MOD = 1e9+7;
int n,k,m,num[N];
ll fp(ll x,ll y){ll ans=1;while(y){if(y&1) ans=ans*x%MOD;x=x*x%MOD;y>>=1;}return ans;}
struct Matrix{
ll a[210][210];
Matrix(){memset(a,0,sizeof(a));}
Matrix cal(const Matrix &A,int n){
Matrix ans;
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
for(int k=0;k<n;++k){
ans.a[i][j]+=a[i][k]*A.a[k][j]%MOD;
ans.a[i][j]%=MOD;
}
return ans;
}
};
Matrix fastm(Matrix a,int m,int n){
Matrix res;
for(int i=0;i<m;++i) res.a[i][i]=1;
while(n){
if(n&1) res=res.cal(a,m);
a=a.cal(a,m);
n>>=1;
}
return res;
}
ll solve(){
Matrix ans,pro; // ans and product
// draw the matrix
for(int i=0;i<m;++i){
int pos = 0;
for(int j=i;j>=0;--j) pro.a[pos++][i]=num[j];
for(int j=m-1;;--j){
if(pos==m) break;
pro.a[pos++][i]=num[j];
}
}
ans = fastm(pro,m,n);
return ans.a[0][0];
}
int main(){
scanf("%d%d%d",&n,&k,&m);
for(int i=1;i<m;++i) num[i] = k/m;
int tmp = k%m;
for(int i=1;i<=tmp;++i) ++num[i];
ll fm = 0; // 分母 和 答案
for(int i=1;i<m;++i) fm += num[i]; // 即一个色子除了%m==0的其余面的数目。
fm=fp(fm,n); // 所有色子搭配的总情况数(分母)
printf("%lld\n",solve()*fp(fm,MOD-2)%MOD);
}
E. Enter to the best problem of this contest!(二叉树)
水题
solver:artist & fashion
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;scanf("%d",&n);
int u=1,x;
printf("1\n");fflush(stdout);scanf("%d",&x);
int left;
while(x!=0){ // 到当前的距离
printf("%d\n",u<<1);fflush(stdout);
scanf("%d",&left); // 询问到左儿子
if(x>left) u=u<<1;
else u=u<<1|1;
x--;
}
printf("! %d\n",u);
}
F. Free restricted flights(图论:最短路)
记录:赛后补题
题意:有一张有向图。两个起点。要到同一个点之后返回。这条路径可以有k段免费,整个过程要最短路。
- 思路:
- dijkstra分别从两个起点到所有的点。dp记录到这个点,花费多少次免费票,最少的cost
- 反向建图,再来一次dijkstra。
- 暴力枚举所有的相遇点,得到结果。
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b,k;
const int INF = 0x3f3f3f3f;
struct edge{
int v,cost;
};
struct node{
int u,cost,used;
bool operator < (const node b)const{
return cost>b.cost;
}
};
const int N = 1e5+6;
int dp1[N][11],dp2[N][11],dp3[N][11],dp4[N][11];
vector<edge>G1[N];
vector<edge>G2[N];
void dijkstra(vector<edge>G[],int st,int dp[][11]){
priority_queue<node> pq;
for(int i=1;i<=n;++i) for(int j=0;j<=k;++j) dp[i][j] = INF;
dp[st][0]=0;
pq.push(node{st,0,0});
while(!pq.empty()){
node cur=pq.top();pq.pop();
for(auto i:G[cur.u]){
if(dp[i.v][cur.used]>cur.cost+i.cost){
dp[i.v][cur.used] = cur.cost+i.cost;
pq.push(node{i.v,dp[i.v][cur.used],cur.used});
}
if(cur.used<k&&dp[i.v][cur.used+1]>cur.cost){
dp[i.v][cur.used+1] = cur.cost;
pq.push(node{i.v,dp[i.v][cur.used+1],cur.used+1});
}
}
}
// 能少用票固然少用票。票不必用完
for(int i=1;i<=n;++i) for(int j=1;j<=k;++j) dp[i][j] = min(dp[i][j],dp[i][j-1]);
}
int main(){
scanf("%d%d",&n,&m);
scanf("%d%d%d",&a,&b,&k);
a++,b++;
for(int i=0;i<m;++i){
int u,v,c;scanf("%d%d%d",&u,&v,&c);
u++,v++;
G1[u].push_back(edge{v,c});
G2[v].push_back(edge{u,c});
}
dijkstra(G1,a,dp1);
dijkstra(G1,b,dp2);
dijkstra(G2,a,dp3);
dijkstra(G2,b,dp4);
int ans = INF,pos = -1;
for(int i=1;i<=n;++i){
// 枚举相遇点
if(i==a||i==b) continue;
int ans1 = INF,ans2 = INF;
// 从a出发到当前点的最短距离
// 以及从b出发到当前点的最短距离
for(int j=0;j<=k;++j){
// 枚举使用的票数
ans1=min(ans1,dp1[i][j]+dp3[i][k-j]);
ans2=min(ans2,dp2[i][j]+dp4[i][k-j]);
}
if(ans1+ans2<ans){
ans = ans1 + ans2;
pos = i;
}
}
if(pos == -1) puts(">:(");
else printf("%d %d\n",pos-1,ans);
}
G. Greater dinner(数学:组合数学)
水题
solver:artist
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9+7;
int main(){
int n,m;scanf("%d%d",&n,&m);
long long ans=1;
int tmp;
for(int i=1;i<=m;++i) scanf("%d",&tmp),scanf("%d",&tmp);
for(int i=1;i<=n;++i) {
ans=ans*i%MOD;
if(m&&ans>2&&ans%2==0) ans/=2,m--;
}
while(m) ans/=2,m--;
printf("%lld\n",ans%MOD);
}
H. Happy game(字符串:manacher+hash)
- 题意:求一个字符串中有多少个本质不同回文子串,且这些子串的长度为奇数
- 思路:用manacher求出所有奇数长度回文子串然后插入hash统计
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+6;
typedef unsigned long long ull;
int n;
ull seed = 31,base[N],_hash[N],d1[N];
char s[N];
map<ull,int>mp;
int ans = 0;
void init(){
base[0] = 1;
for(int i=1;i<N;++i) base[i] = base[i-1]*seed;
}
void makehash(int len,char str[]){
for(int i=1;i<=len;++i) _hash[i] = _hash[i-1]*seed+(str[i]-'a'+1);
}
// 从i开始,长度为l
ull gethash(int i,int l){
return _hash[i+l-1]-_hash[i-1]*base[l];
}
void insert(int i,int l){
if(l==1) return;
ull tmp = gethash(i,l);
if(mp.find(tmp)==mp.end()) mp[tmp]=1,ans++;
}
void manacher_odd(char str[]){
for(int i=1,l=1,r=0;i<=n;++i){
int k = (i>r)?1:min(d1[l+r-i],r-i);
while(1<=i-k&&i+k<=n&&str[i-k]==str[i+k]) k++,insert(i-k+1,2*k-1);
d1[i] = k--;
if(i+k>r) l=i-k,r=i+k;
}
}
int main(){
scanf("%d",&n);
scanf("%s",s+1);
init();
makehash(n,s);
manacher_odd(s);
printf("%d\n",ans);
}
K. Katastrophic sort
水题。
solver:david
# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
char s1[maxn],s2[maxn];
int main(){
scanf("%s",s1+1);
scanf("%s",s2+1);
int l1=strlen(s1+1),l2=strlen(s2+1);
int i;
bool tag = 0;
for(i=1;i<=l1&&i<=l2;++i){
if(s1[i]>='0' && s1[i] <= '9'){
tag = 1;
break;
}
else{
if(s1[i] < s2[i]){
printf("<\n");
return 0;
}
else if(s1[i] > s2[i]){
printf(">\n");
return 0;
}
}
}
if(tag){
if(l1 > l2){
printf(">\n");
return 0;
}
else if(l1 < l2) {
printf("<\n");
return 0;
}
else{
for(;i<=l1;++i){
if(s1[i] < s2[i]){
printf("<\n");
return 0;
}
else if(s1[i] > s2[i]){
printf(">\n");
return 0;
}
}
printf("=\n");
}
}
else{
printf("=\n");
return 0;
}
return 0;
}
L. Lonely day(图论:BFS+记录路径)
solver:david
code:artist
思路
- 题意:一个地图,S是起点,E是终点。从S走到E,只能走干净的点。但如果前面(直的)有一串脏点后有一个干净的点,可以一步跳过去。求最短到达E的路径。
- 思路:因为要记录路径,因此手写队列。注意,同样长度的路径选取字典序小的,那么我们先Down,再left,再right,最后up。注意:数组要开得足够大,路径可能长度为N^2.
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3+5;
int n,m;
int dir[4][2]={
{1,0},
{0,-1},
{0,1},
{-1,0}
};
bool check(int x,int y){
return x<=n&&x>=1&&y<=m&&y>=1;
}
char mp[N][N];
int vis[N][N]; // 记录曾经是否走过这个点
int sx,sy; // start
struct node{
int x,y;
int fa; // 前驱
};
node path[N*N]; // 要记录路径,须手工模拟队列
char ans[N*N];
void print(int pos){
node cur,last;
int cnt = 0;
cur = path[pos];
pos = cur.fa;
while(pos!=-1){
last = path[pos];
if(cur.x>last.x) ans[cnt++] = 'D';
if(cur.x<last.x) ans[cnt++] = 'U';
if(cur.y>last.y) ans[cnt++] = 'R';
if(cur.y<last.y) ans[cnt++] = 'L';
pos = last.fa;
cur = last;
}
printf("%d\n",cnt);
for(int i=cnt-1;i>=0;--i) printf("%c",ans[i]);
}
void BFS(){
node start,next;
start.x = sx,start.y = sy,start.fa=-1;
int front = 0,rear = 0; // 队列头以及队列尾
path[rear++] = start;
vis[sx][sy] = 1;
while(front<rear){
start = path[front++];
for(int i=0;i<4;++i){
next.x=start.x+dir[i][0],next.y=start.y+dir[i][1];
if(check(next.x,next.y)&&!vis[next.x][next.y]&&mp[next.x][next.y]!='X'){
next.fa = front-1;
path[rear++]=next;
vis[next.x][next.y]=1;
if(mp[next.x][next.y]=='E') {
print(rear-1);return;
}
}
while(check(next.x,next.y)&&mp[next.x][next.y]=='X')
next.x=next.x+dir[i][0],next.y=next.y+dir[i][1];
if(check(next.x,next.y)&&!vis[next.x][next.y]){
next.fa = front-1;
path[rear++]=next;
vis[next.x][next.y]=1;
if(mp[next.x][next.y]=='E'){
print(rear-1);return;
}
}
}
}
puts("-1");
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%s",mp[i]+1);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(mp[i][j]=='S') sx = i,sy = j;
BFS();
}
M. Magic cells(字符串:子序列自动机)
solver: david
- 题意:s的子序列中,如果有ai的前缀,输出。如果没有,输出impossible
- 思路:从后往前扫s,记录每个字母在每个位置之后最前出现的position
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
char s[N<<1],a[N<<1];
int n;
int pos[N<<1][28]; // 记录当前位置
void solve(){
int len = strlen(s+1);
int nowlast[28];
for(int i=0;i<26;++i) nowlast[i]=-1;
for(int i=len;i>=0;--i){
for(int j=0;j<26;++j) pos[i][j]=nowlast[j];
nowlast[s[i]-'a']=i;
}
}
int main(){
scanf("%s",s+1);
solve();
scanf("%d",&n);
while(n--){
scanf("%s",a+1);
int len = strlen(a+1),p = 0; // 当前在s中所处的位置
int len2=0;
for(int i=1;i<=len;++i){
p=pos[p][a[i]-'a'];
if(p==-1) break;
printf("%c",a[i]);
len2++;
}
if(len2) puts("");else puts("IMPOSSIBLE");
}
}
unsolved
C. Cipher count
挖坑。
题意:至少有多少种不同的字符串(长度小于等于k,字母种类为a种)。
定义不同:短的重复可以得到长的,那么他们相同。
思路:据说是容斥
I. Incredible photography
WA on test 13
思路:
(1)
找左边第一个严格大于他,且连续相同中最左一个的位置:
思路有点像树状数组求逆序对:
先找到位置,再插入i(当前)。
我们把h数组变为INF-h,使得越大的越小。
g数组储存 从小到大,这个(本质为i左边的h从大到小)排序序号的值,且同样的值,储存的位置更小。
查找的时候,lower_bound,即找到第一个大于等于INF-h[i]的值。即第一个小于等于h[i]的值。往左走一格,即第一个严格大于h[i]的值的位置。
las记录这个位置的值在原数组中的位置。
现在考虑把当前插入g:如果las[k]没有数字,或这个位置上的数字不等于当前,就改。因为他必然更大。如果等于的话,不改,因为储存位置更左的值。
(2)用dfs记忆化搜索。
# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
const int INF = 1e9+10;
int n;
int h[maxn];
int g[maxn];
int las[maxn];
int lt[maxn],rt[maxn];
long long dp[maxn];
long long dfs(int pos){
if(dp[pos]) return dp[pos];
if(lt[pos]==0&&rt[pos]==n+1) return 0;
if(lt[pos]==0) return dp[pos]=dfs(rt[pos])+rt[pos]-pos;
if(rt[pos]==n+1) return dp[pos]=dfs(lt[pos])+pos-lt[pos];
return dp[pos] = max(dfs(lt[pos])+pos-lt[pos],dfs(rt[pos])+rt[pos]-pos);
}
int main(){
scanf("%d",&n);
// 预处理每个点向左向右,第一个严格大于它的,连续相同的取最后一个。
for(int i=1;i<=n;++i) scanf("%d",h+i),h[i]=1000000001-h[i];
h[0] = -1;
g[0] = 0;
for(int i=1;i<=n;++i) g[i] = INF;
for(int i=1;i<=n;++i){
int k = lower_bound(g+1,g+n+1,h[i]) - g;
lt[i] = las[k-1];
if(las[k] == 0 || h[las[k]] != h[i]) {
g[k] = h[i];
las[k] = i;
}
}
// 找往右第一个严格大于,跟把h数组对调,找往左第一个严格大于没有区别。
reverse(h+1,h+n+1);
g[0] = 0;
for(int i=1;i<=n;++i) g[i] = INF;
for(int i=0;i<=n;++i) las[i]=0;
for(int i=1;i<=n;++i){
int k= lower_bound(g+1,g+n+1,h[i]) - g;
rt[n-i+1] = n-las[k-1]+1;
if(las[k] == n || h[las[k]] != h[i]) {
g[k] = h[i];
las[k] = i;
}
}
for(int i=1;i<=n;++i) dfs(i);
for(int i=1;i<n;++i) printf("%lld ",dp[i]);
printf("%lld\n",dp[n]);
return 0;
}