题目是按通过量做的,所以题号可能不太对。
1.Vacation
这种问题一定不能思考的太复杂,不要把自己绕进去。
我们就简单的将其分成两类:
(1) 前面没车堵我。
(2) 前面全部都堵我。
对于第一种情况,没车堵我,那么显然就是s[0]/v[0]。
重点关注第二种情况,前面所有的车都堵我。
思考了五秒钟以后我们发现,前面所有的车都堵我是啥意思,就是第一辆车最慢呗。
那么答案是什么呢?就是(s[n]+sum[n])/v[n]。因为n是第一辆车。
这样我们就可以观察出来,我的速度是受别人的限制的。
所以我们枚举每一辆前面的车,看看它会不会堵我就行了。
复杂度O(n)
#include
using namespace std;
const int maxn = 1e5+7;
double l[maxn],s[maxn],v[maxn];
double sum[maxn];
int n;
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<=n;i++) scanf("%lf",l+i);
for(int i=0;i<=n;i++) scanf("%lf",s+i);
for(int i=0;i<=n;i++) scanf("%lf",v+i);
double ans = s[0]/v[0];
sum[0] = 0;
for(int i=1;i<=n;i++)
{
sum[i] = sum[i-1] + l[i];
ans = max(ans,(s[i]+sum[i])/v[i]);
}
printf("%.6lf\n",ans);
}
return 0;
}
2.Path
很明显先求一个最短路,然后在最短路的边上求最小割就完事了。
#include
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e4+7;
struct node
{
int to;
int cost;
};
vector<node> V[maxn];
int n,m;
int x[maxn],y[maxn],z[maxn];
ll dis[maxn];
struct edge
{
int to;
ll cap;
int rev;
};
vector<edge> G[maxn];
int depth[maxn],arc[maxn];
void add_edge(int from,int to,ll cost)
{
G[from].push_back((edge){to,cost,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
void bfs(int s)
{
queue<int> q;
memset(depth,-1,sizeof(depth));
depth[s] = 0;
q.push(s);
while(!q.empty())
{
int v = q.front();
q.pop();
for(int i=0;i<G[v].size();i++)
{
edge &e = G[v][i];
if(e.cap>0 && depth[e.to]<0)
{
depth[e.to] = depth[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t) return f;
for(int &i=arc[v];i<G[v].size();i++)
{
edge &e = G[v][i];
if(e.cap>0 && depth[e.to]==depth[v]+1)
{
ll d = dfs(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap -= d;
G[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll flow = 0;
while(true)
{
bfs(s);
if(depth[t]<0) return flow;
memset(arc,0,sizeof(arc));
ll f;
while((f=dfs(s,t,INF))>0)
{
flow += f;
}
}
}
void dijkstra(int s)
{
struct edge
{
int now;
int cost;
bool operator<(const edge &rhs)const
{
return cost>rhs.cost;
}
};
memset(dis,INF,sizeof(dis));
dis[s] = 0;
priority_queue<edge> q;
q.push({s,dis[s]});
while(!q.empty())
{
edge tmp = q.top();
q.pop();
for(int i=0;i<V[tmp.now].size();i++)
{
int v = V[tmp.now][i].to;
if(dis[v]>dis[tmp.now]+V[tmp.now][i].cost)
{
dis[v] = dis[tmp.now] + V[tmp.now][i].cost;
q.push({v,dis[v]});
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(G,0,sizeof(G));
memset(V,0,sizeof(V));
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",x+i,y+i,z+i);
V[x[i]].push_back({y[i],z[i]});
}
dijkstra(1);
for(int i=1;i<=n;i++)
{
for(int j=0;j<V[i].size();j++)
{
int v = V[i][j].to;
if(dis[v]==dis[i]+V[i][j].cost) add_edge(i,v,V[i][j].cost);
}
}
printf("%d\n",dinic(1,n));
}
return 0;
}
3.Operation
分析:
构造线性基,在区间[l,r]查询最大值,我们需要尽量将位置往左边放,询问的时候判断一下位置是否在L右边就行了。
具体还是看代码吧:
#include
using namespace std;
const int maxn = 5e5+7;
int n,m;
int a[maxn],p[maxn][32],pos[maxn][32];
void insert(int x,int id)
{
int tmp = id;
for(int i=31;i>=0;i--)
{
p[id][i] = p[id-1][i];
pos[id][i] = pos[id-1][i];
}
for(int i=31;i>=0;i--)
{
if(!(x>>i)) continue;
if(!p[id][i])
{
p[id][i] = x;
pos[id][i] = tmp;
break;
}
else
{
if(pos[id][i]<tmp)
{
swap(pos[id][i],tmp);
swap(p[id][i],x);
}
x ^= p[id][i];
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(p,0,sizeof(p));
memset(pos,0,sizeof(pos));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
insert(a[i],i);
}
int lans = 0;
for(int i=0;i<m;i++)
{
int op,x,y;
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&x,&y);
x = (x^lans)%n+1;
y = (y^lans)%n+1;
if(x>y) swap(x,y);
int ans = 0;
for(int i=31;i>=0;i--)
{
if(ans<(ans^p[y][i]) && pos[y][i]>=x) ans = ans^p[y][i];
}
printf("%d\n",ans);
lans = ans;
}
else
{
scanf("%d",&x);
x ^= lans;
insert(x,++n);
}
}
}
return 0;
}
4.String
序列自动机预处理每一个字母后缀出现的次数,预处理每个字母的后一个相同的字母出现的位置。
然后就判断合法即可。
复杂度:O(n2626)
#include
using namespace std;
const int maxn = 3e5+7;
int k;
char s[maxn],ans[maxn];
int cnt[maxn][30],nxt[maxn][30],num[maxn],x[maxn],y[maxn];
bool check(int pos,int nlen)
{
int len = 0,ned = 0;
for(int i=0;i<26;i++)
{
if(num[i]+cnt[pos][i]<x[i]) return false;
len += num[i] + min(y[i]-num[i],cnt[pos][i]);
ned += max(0,x[i]-num[i]);
}
if(len<k) return false;
if(ned>k-nlen) return false;
return true;
}
int main()
{
while(~scanf("%s%d",s+1,&k))
{
for(int i=0;i<26;i++) scanf("%d%d",x+i,y+i);
int len = strlen(s+1);
memset(cnt,0,sizeof(cnt));
memset(nxt,-1,sizeof(nxt));
memset(num,0,sizeof(num));
for(int i=len-1;i>=0;i--)
{
for(int j=0;j<26;j++)
{
nxt[i][j] = nxt[i+1][j];
cnt[i][j] = cnt[i+1][j];
}
nxt[i][s[i+1]-'a'] = i+1;
cnt[i][s[i+1]-'a']++;
}
int pos = 0,tot = 0;
bool isok = true;
while(pos<=len && tot<k)
{
bool found = false;
for(int i=0;i<26;i++)
{
if(nxt[pos][i]!=-1 && num[i]<y[i])
{
num[i]++;
if(check(nxt[pos][i],tot+1))
{
found = true;
ans[tot++] = i+'a';
pos = nxt[pos][i];
break;
}
num[i]--;
}
}
if(!found)
{
isok = false;
break;
}
}
ans[tot] = '\0';
if(isok) printf("%s\n",ans);
else puts("-1");
}
return 0;
}
5.Blank
比较经典的dp
先算完答案再将不合法的答案删去的策略。
设dp[t][i][j][k]表示前t个,{0,1,2,3}出现的最后位置 (t,i,j,k不对应0,1,2,3)
且 t>i>j>k
那么就有以下四个转移:
t选t-1这个位置的数:dp[t][i][j][k]
t选i这个位置的数:dp[t][t-1][j][k]
t选j这个位置的数:dp[t][t-1][i][k]
t选k这个位置的数:dp[t][t-1][i][j]
然后枚举右端点,计数i,j,k中>l的个数,不等于x的dp置为0,即去掉了不合法的情况。
#include
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector
#define vl vector
#define pb push_back
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 110;
const int mod = 998244353;
ll dp[2][maxn][maxn][maxn];
int n,m;
struct node
{
int l,x;
};
vector<node> v[maxn];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(v,0,sizeof(v));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
v[r].push_back({l,x});
}
int c = 0;
dp[c][0][0][0] = 1;
for(int t=1;t<=n;t++)
{
c ^= 1;
for(int i=0;i<=t;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<=j;k++)
{
dp[c][i][j][k] = 0;
}
}
}
for(int i=0;i<t;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<=j;k++)
{
dp[c][i][j][k] = (dp[c][i][j][k]+dp[c^1][i][j][k])%mod;
dp[c][t-1][j][k] = (dp[c][t-1][j][k]+dp[c^1][i][j][k])%mod;
dp[c][t-1][i][k] = (dp[c][t-1][i][k]+dp[c^1][i][j][k])%mod;
dp[c][t-1][i][j] = (dp[c][t-1][i][j]+dp[c^1][i][j][k])%mod;
}
}
}
for(int p=0;p<v[t].size();p++)
{
int l = v[t][p].l,x = v[t][p].x;
for(int i=0;i<=t;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<=j;k++)
{
int cnt = 1;
if(i>=l) cnt++;
if(j>=l) cnt++;
if(k>=l) cnt++;
if(cnt!=x) dp[c][i][j][k] = 0;
}
}
}
}
}
ll ans = 0;
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
for(int k=0;k<=j;k++)
{
ans = (ans+dp[c][i][j][k])%mod;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
6.Code
给两类点,能否找到一条直线将其划分为两部分。
判断凸包交的裸题。
凸包交时当且仅当:
1.凸包上的任意点都在另一个凸包的外面
2.凸包的任意线段都与另一个凸包不相交
#include
using namespace std;
const double eps = 1e-8;
const double pi = acos(-1.0);
class Point {
public:
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
Point operator+(Point a) {
return Point(a.x + x, a.y + y);
}
Point operator-(Point a) {
return Point(x - a.x, y - a.y);
}
bool operator<(const Point &a) const {
if (x == a.x)
return y < a.y;
return x < a.x;
}
bool operator==(const Point &a) const {
if (fabs(x - a.x) < eps && fabs(y - a.y) < eps)
return 1;
return 0;
}
double length() {
return sqrt(x * x + y * y);
}
};
typedef Point Vector;
double cross(Vector a, Vector b) {
return a.x * b.y - a.y * b.x;
}
double dot(Vector a, Vector b) {
return a.x * b.x + a.y * b.y;
}
bool isclock(Point p0, Point p1, Point p2) {
Vector a = p1 - p0;
Vector b = p2 - p0;
if (cross(a, b) < -eps)
return true;
return false;
}
double getDistance(Point a, Point b) {
return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}
typedef vector<Point> Polygon;
Polygon Andrew(Polygon s) {
Polygon u, l;
if(s.size() < 3) return s;
sort(s.begin(), s.end());
u.push_back(s[0]);
u.push_back(s[1]);
l.push_back(s[s.size() - 1]);
l.push_back(s[s.size() - 2]);
for(int i = 2 ; i < s.size() ; ++i) {
for(int n = u.size() ; n >= 2 && !isclock(u[n - 2], u[n - 1], s[i]); --n) {
u.pop_back();
}
u.push_back(s[i]);
}
for(int i = s.size() - 3 ; i >= 0 ; --i) {
for(int n = l.size() ; n >=2 && !isclock(l[n-2],l[n-1],s[i]); --n) {
l.pop_back();
}
l.push_back(s[i]);
}
for(int i = 1 ; i < u.size() - 1 ; i++) l.push_back(u[i]);
return l;
}
int dcmp(double x) {
if (fabs(x) <= eps)
return 0;
return x > 0 ? 1 : -1;
}
// 判断点在线段上
bool OnSegment(Point p, Point a1, Point a2) {
return dcmp(cross(a1 - p, a2 - p)) == 0 && dcmp(dot(a1 - p, a2 - p)) < 0;
}
// 判断线段相交
bool Intersection(Point a1, Point a2, Point b1, Point b2) {
double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1),
c3 = cross(b2 - b1, a1 - b1), c4 = cross(b2 - b1, a2 - b1);
return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
}
// 判断点在凸包内
int isPointInPolygon(Point p, vector<Point> s) {
int wn = 0, cc = s.size();
for (int i = 0; i < cc; i++) {
Point p1 = s[i];
Point p2 = s[(i + 1) % cc];
if (p1 == p || p2 == p || OnSegment(p, p1, p2)) return -1;
int k = dcmp(cross(p2 - p1, p - p1));
int d1 = dcmp(p1.y - p.y);
int d2 = dcmp(p2.y - p.y);
if (k > 0 && d1 <= 0 && d2 > 0) wn++;
if (k < 0 && d2 <= 0 && d1 > 0) wn--;
}
if (wn != 0) return 1;
return 0;
}
void solve(Polygon s1, Polygon s2) {
int c1 = s1.size(), c2 = s2.size();
for(int i = 0; i < c1; ++i) {
if(isPointInPolygon(s1[i], s2)) {
printf("Infinite loop!\n");
return;
}
}
for(int i = 0; i < c2; ++i) {
if(isPointInPolygon(s2[i], s1)) {
printf("Infinite loop!\n");
return;
}
}
for (int i = 0; i < c1; i++) {
for (int j = 0; j < c2; j++) {
if (Intersection(s1[i], s1[(i + 1) % c1], s2[j], s2[(j + 1) % c2])) {
printf("Infinite loop!\n");
return;
}
}
}
printf("Successful!\n");
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
Polygon s1, s2;
for(int i=0;i<n;i++)
{
double x,y;
int op;
scanf("%lf%lf%d",&x,&y,&op);
if(op==1) s1.push_back(Point(x,y));
else s2.push_back(Point(x,y));
}
if(s1.size()) s1 = Andrew(s1);
if(s2.size()) s2 = Andrew(s2);
solve(s1,s2);
}
return 0;
}
7.Typewriter
设dp[i]表示打印到第i位的最小花费
转移也很简单: dp[i] = min(dp[i-1]+p,dp[j]+q)
将字母一个一个放进后缀自动机里面,判断前缀里面是否包含后缀。
值得注意的是,对于每一个pos,(len[fa[pos]]+1 ~ len[pos]) 即是该子串的所有长度。
在判断边的时候需要结合长度判断。
引用Jiaaaaaaaqi大佬的解释方式:
/*
如何维护 pos 在符合条件的范围内,我们知道 pos 节点包含了同样性质的长度从 len[fa[pos]]+1 到 len[pos] 内的子串,我们只要保证这里面最短的子串 len[fa[pos]]+1 不要太长,需要可以表示出后面那部分的串。在插入 s[r] 之前,此时只到 r−1,需要保证 len[fa[pos]]+1≤(r−1)−(l+1)+1,插入 s[r] 后,此时到了 r,需要保证 len[fa[pos]]+1≤r−(l+1)+1
*/
#include
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector
#define vl vector
#define pb push_back
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e5+7;
char s[maxn];
ll dp[maxn];
struct node
{
int ch[26];
int len,fa;
}point[maxn<<1];
int las = 1,tot = 1;
int siz[maxn<<1];
void add(char c)
{
int p = las;
int np = las = ++tot;
siz[tot] = 1;
point[np].len = point[p].len+1;
for(;p && !point[p].ch[c];p=point[p].fa) point[p].ch[c] = np;
if(!p) point[np].fa = 1;
else
{
int q = point[p].ch[c];
if(point[q].len==point[p].len+1) point[np].fa = q;
else
{
int nq = ++tot;
point[nq] = point[q];
point[nq].len = point[p].len+1;
point[q].fa = point[np].fa = nq;
for(;p && point[p].ch[c]==q;p=point[p].fa) point[p].ch[c] = nq;
}
}
}
void init()
{
memset(point,0,sizeof(point));
memset(siz,0,sizeof(siz));
las = 1,tot = 1;
memset(dp,0,sizeof(dp));
}
int main()
{
while(~scanf("%s",s+1))
{
init();
ll w1,w2;
scanf("%lld%lld",&w1,&w2);
int n = strlen(s+1);
int p = 1,l = 1;
for(int i = 1;i<=n;i++)
{
dp[i] = dp[i-1]+w1;
int c = s[i]-'a';
while((!point[p].ch[c] || i-l+1>l-1) && l<=i)
{
add(s[l++]-'a');
while(p && point[point[p].fa].len>=i-l) p = point[p].fa;
if(!p) p = 1;
}
p = point[p].ch[c];
while(p && point[point[p].fa].len>=i-l+1) p = point[p].fa;
if(!p) p = 1;
if(l<=i) dp[i] = min(dp[i],dp[l-1]+w2);
}
printf("%lld\n",dp[n]);
}
return 0;
}
#include
using namespace std;
typedef long long ll;
const int maxn = 2e6+7;
const int mod = 998244353;
const int G = 3;
int n,m,L,R[maxn];
int A[maxn],B[maxn];
ll fac[maxn],inv[maxn];
int cnt[10];
ll qpow(ll a,ll b)
{
ll ans = 1;
while(b>0)
{
if(b&1) ans = ans*a%mod;
b>>=1;
a = a*a%mod;
}
return ans%mod;
}
void init()
{
fac[0] = fac[1] = 1;
for(int i=2;i<maxn;i++) fac[i] = 1LL*fac[i-1]*i%mod;
inv[maxn-1] = qpow(fac[maxn-1],mod-2);
for(int i=maxn-2;i>=0;i--) inv[i] = 1LL*inv[i+1]*(i+1)%mod;
}
ll C(int n,int m)
{
if(m>n) return 0;
return 1LL*fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void NTT(int *a,int f)
{
for(int i = 0; i < n; i++)
{
if(i < R[i]) swap(a[i],a[R[i]]);
}
for(int i = 1; i < n; i <<= 1)
{
ll gn = qpow(G,(mod - 1) / (i << 1));
for(int j = 0; j < n; j += (i << 1))
{
ll g = 1;
for(int k = 0; k < i; k++, g = g * gn % mod)
{
int x = a[j + k], y = g * a[j + k + i] % mod;
a[j + k] = (x + y) % mod;
a[j + k + i] = (x - y + mod) % mod;
}
}
}
if(f==1) return;
int inv = qpow(n,mod - 2);
reverse(a + 1,a + n);
for(int i = 0; i < n; i++) a[i] = 1ll * a[i] * inv % mod;
}
ll solve(int *A,int *B)
{
L = 0;
ll ans = 0;
int tmp = n,m = n;
m = n + m;
for(n = 1; n <= m; n <<= 1) L++;
for(int i = 0; i < n; i++) R[i] = (R[i >> 1] >> 1) | ((i & 1) << (L - 1));
for(int i=1;i<=3;i++)
{
if(!cnt[i]) continue;
for(int j=0;j*i<=tmp;j++) B[i*j] = C(cnt[i]+j-1,j);
NTT(A,1);
NTT(B,1);
for(int i = 0; i < n; i++) A[i] = 1ll * A[i] * B[i] % mod;
NTT(A,-1);
NTT(B,-1);
for(int i=0;i<=n+1;i++) B[i] = 0;
for(int i=tmp+1;i<=n+1;i++) A[i] = 0;
}
for(int i=0;i<=tmp;i++) ans ^= (1LL*(i+1)*A[i]);
return ans;
}
int main()
{
init();
int T;
scanf("%d",&T);
while(T--)
{
memset(cnt,0,sizeof(cnt));
scanf("%d%d",&n,&m);
n--;
for(int i=0;i<=n;i++) scanf("%d",A+i);
for(int i=1;i<=m;i++)
{
int tmp;
scanf("%d",&tmp);
cnt[tmp]++;
}
printf("%lld\n",solve(A,B));
}
return 0;
}