1001 Blank
题意: 求长度为
n
,
n
≤
100
n,n \leq 100
n,n≤100,满足
[
l
,
r
]
[l,r]
[l,r] 中有
x
x
x个不同元素的数组的个数
(
1
≤
x
≤
4
)
(1\leq x\leq4)
(1≤x≤4)
分析: 注意到数组的范围很小,容易联想到dp来做,关键是怎么确定状态呢?
考虑到计数问题填每一个位置的方案数以及题目
[
l
,
r
]
[l,r]
[l,r]的限制,可以考虑维护状态这四个元素最后一次出现的位置
d
p
[
j
]
[
k
]
[
l
]
[
i
]
,
j
<
k
<
l
<
i
dp[j][k][l][i],j < k < l < i
dp[j][k][l][i],j<k<l<i 即可,有一个小trick就是可以用滚动数组,因为有一维已经确定了,肯定是
i
−
1
i-1
i−1
d
p
[
l
]
[
k
]
[
i
−
1
]
[
i
]
=
d
p
[
l
]
[
k
]
[
i
−
1
]
[
k
]
+
d
p
[
j
]
[
l
]
[
k
]
[
i
−
1
]
dp[l][k][i-1][i] = dp[l][k][i-1][k]+dp[j][l][k][i-1]
dp[l][k][i−1][i]=dp[l][k][i−1][k]+dp[j][l][k][i−1]
d
p
[
j
]
[
k
]
[
i
−
1
]
[
i
]
=
d
p
[
j
]
[
k
]
[
i
−
1
]
[
i
]
+
d
p
[
j
]
[
l
]
[
k
]
[
i
−
1
]
dp[j][k][i-1][i] = dp[j][k][i-1][i] + dp[j][l][k][i-1]
dp[j][k][i−1][i]=dp[j][k][i−1][i]+dp[j][l][k][i−1]
d
p
[
j
]
[
l
]
[
i
−
1
]
[
i
]
=
d
p
[
j
]
[
l
]
[
i
−
1
]
[
i
]
+
d
p
[
j
]
[
l
]
[
k
]
[
i
−
1
]
dp[j][l][i-1][i] = dp[j][l][i-1][i] +dp[j][l][k][i-1]
dp[j][l][i−1][i]=dp[j][l][i−1][i]+dp[j][l][k][i−1]
d
p
[
j
]
[
k
]
[
l
]
[
i
]
=
d
p
[
j
]
[
k
]
[
l
]
[
i
]
+
d
p
[
j
]
[
k
]
[
l
]
[
i
−
1
]
dp[j][k][l][i] = dp[j][k][l][i]+dp[j][k][l][i-1]
dp[j][k][l][i]=dp[j][k][l][i]+dp[j][k][l][i−1]
参考代码:
typedef pair<int,int> P;
const int maxn = 100+2;
const int N = maxn;
int dp[2][N][N][N];
int (*f)[N][N] = dp[0];
int (*g)[N][N] = dp[1];
vector<P> G[maxn];
// int A[maxn];
inline void Add(int &a,int b){
a += b;
if(a >= mod)
a -= mod;
}
void solve(){
int n,m;
scanf("%d%d",&n,&m);
// memset()
for(int i = 1;i <= n;++i)
{
G[i].clear();
}
while(m--){
int r,l,num;
scanf("%d%d%d",&l,&r,&num);
G[r].Pb(P(l,num));
}
f[0][0][0] = 1;
for(int i = 1;i<= n; ++i){
swap(g,f);
for(int j = 0;j < i; ++j){
for(int k = j;k < i; ++k){
for(int l = k;l < i; ++l)
{
int &a = g[j][k][l];
if(a){
Add(f[k][l][i-1],a);
Add(f[j][l][i-1],a);
Add(f[j][k][i-1],a);
Add(f[j][k][l],a);
}
g[j][k][l] = 0;
}
}
}
for(int j = 0;j < i; ++j){
for(int k = j;k < i; ++k){
for(int l = k; l < i; ++l){
for(auto &c:G[i]){
int t = 0;
t += i >= c.first;
t += j >= c.first;
t += k >= c.first;
t += l >= c.first;
// cout<<t<<endl;
if(t != c.second) f[j][k][l] = 0;
}
}
}
}
}
int ans = 0;
for(int i = 0;i < n; ++i){
for(int j = i;j < n; ++j){
for(int k = j;k < n; ++k){
Add(ans,f[i][j][k]);
f[i][j][k] = 0;
}
}
}
cout<<ans<<endl;
}
int main(void)
{
int T;cin>>T;
while(T--){
solve();
}
return 0;
}
1002 Operation
题意:
有两种操作:
0
,
l
,
r
0,l,r
0,l,r 查询 [l,r] 中数能异或出来的最大值
1
,
x
1,x
1,x 在末尾插入一个新的x
分析:
由于不是连续的,所以不能用trie树来做,只能通过线形基,考虑对每一个位置贪心的维护一个线形基,同时维护位置,使得位置尽量靠右即可
参考代码
const int maxn = 1e6+10;
int a[maxn][31],loc[maxn][31];
void ins(int x,int p){
memcpy(a[x],a[x-1],sizeof(a[x]));
memcpy(loc[x],loc[x-1],sizeof(loc[x]));
int xx = x;
// cout<<p<<endl;
for(int i = 30;i >= 0; --i){
// cout<<x<<" "<<i<<" "<<p<<endl;
if((1<<i)&p){
if(!a[x][i]){
a[x][i] = p;
loc[x][i] = xx;
return ;
}
if(loc[x][i] < xx){
swap(a[x][i],p);
swap(loc[x][i],xx);
}
p ^= a[x][i];
}
}
}
int query(int l,int r){
int ans = 0;
for(int i = 30; i >= 0; --i){
if(a[r][i] && loc[r][i] >= l)
ans = max(ans,ans ^ a[r][i]);
// cout<<r<<" "<<i<<" "<<a[r][i]<<" "<<loc[r][i]<<endl;
}
return ans;
}
int A[maxn];
int main(void)
{
int n,m;
int T;
cin>>T;
while(T--){
cin>>n>>m;
memset(a,0,sizeof(a));
for(int i = 1;i <= n; ++i){
scanf("%d",&A[i]);
ins(i,A[i]);
}
int lastans = 0;
while(m--){
int op,x,l,r;
scanf("%d",&op);
if(op ==0){
scanf("%d%d",&l,&r);
l = (l^lastans)%n+1;
r = (r^lastans)%n+1;
if(l > r)
swap(l,r);
printf("%d\n",lastans = query(l,r));
}
else{
scanf("%d",&x);
++n;
x ^= lastans;
ins(n,x);
}
}
}
return 0;
}
1004 vocation
题意:
给定n+1辆车的最大速度,长度,相对于终点初始位置,求最后一辆车通过终点的时间
分析:
对于最后一辆车,考虑它最后和谁合体的最前面那一辆车,这辆车没有与其他车辆相撞,所以他的到达终点的速度和时间已知,最后一辆车到达的时间正好是这辆车的速度跑完中间所有车的长度
参考代码
while(cin>>n){
for(int i = 0;i <= n; ++i)
scanf("%d",&l[i]);
for(int i = 0;i <= n; ++i)
scanf("%d",&s[i]);
for(int i = 0;i <= n; ++i)
scanf("%d",&v[i]);
double ans = 1.0*s[0]/v[0];
double sum = 0.0;
for(int i = 1;i <= n; ++i){
ans = max(ans,1.00*(s[i]+(sum+=l[i]))/v[i]);
}
printf("%.10f\n",ans);
}
1005 Path
题意: 有一个n个点和m条边的有向图,毁坏一条边的代价是它的边权,问使得从
1
−
>
n
1->n
1−>n的最短路增大需要的最少代价是多少
分析: 考虑抠出来所有的最短路上的边,然后跑求最小割即可,复杂度玄学
参考代码
1010 string
题意:有一个字符串长度为n,求一个长度为k的子序列k,每一个字符的数量有下界L和上界R, 求一个字典序最小的
分析: 字典序最小的子序列通常都是枚举每一位放什么即可
const int maxn = 1e5 + 10;
char ar[maxn];
int used[maxn];
int nxt[maxn][26];
vector<int> vec[26];
int head[26],L[26],R[26];
int k;
void solve() {
for(int i = 0;i < 26; ++i)
scanf("%d%d",&L[i],&R[i]);
for (int i = 0; i < 26; ++i)
vec[i].clear();
me(used),me(head);
int n = strlen(ar);
me(nxt[n]);
for (int i = n - 1; i >= 0; --i) {
for (int j = 0; j < 26; ++j)
nxt[i][j] = nxt[i + 1][j];
nxt[i][ar[i] - 'a']++;
}
for (int i = 0; i < n; ++i)
vec[ar[i] - 'a'].push_back(i);
// DEBUG;
int now = -1;
vector<int> v;
for (int i = 0; i < k; ++i) {
// cout<<i<<endl;
bool yes = 0;
for (int j = 0; j < 26; ++j) { // 枚举这一位放什么
bool flag = true;
while (head[j] < (int)vec[j].size() && vec[j][head[j]] < now)
{
head[j]++;
// cout<<j<<endl;
}
if(head[j] == vec[j].size()) continue;
if(used[j] >= R[j]) continue;
int pos = vec[j][head[j]];
int sum = 0;
used[j]++;
for(int k = 0;k < 26; ++k)
sum += max(L[k]-used[k],0);
if(sum > k-i-1)
flag = false;
for(int i = 0;i < 26; ++i)
if(nxt[pos+1][i] < L[i]-used[i])
flag = false;
if(!flag)
used[j]--;
else
{
yes = true;
now = pos;
head[j]++;
v.push_back(now);
break;
}
}
if(!yes){
puts("-1");
return ;
}
// else
// now = pos;
}
for(auto c: v)
putchar(ar[c]);
puts("");
}
int main(void)
{
while (~scanf("%s%d", ar, &k)) {
solve();
}
return 0;
}
1011 Function
题意:
分析:
- 考虑将三次根号去掉,分块 [ 1 , 2 3 − 1 ] , [ 2 3 , 3 3 − 1 ] , . . . . [ i 3 , i 3 − 1 ] . . . , [ j 3 , n ] [1,2^3-1],[2^3,3^3-1],....[i^3,i^3-1]...,[j^3,n] [1,23−1],[23,33−1],....[i3,i3−1]...,[j3,n],
- 每一块的大小 ( i + 1 ) 3 − 1 − i 3 + 1 = 3 ∗ i 2 + 3 ∗ i + 1 = i ( 3 i + 3 ) + 1 (i+1)^3-1-i^3+1 = 3*i^2+3*i+1 = i(3i+3)+1 (i+1)3−1−i3+1=3∗i2+3∗i+1=i(3i+3)+1,其中每i个数和i的 g c d s u m gcd_{sum} gcdsum都相同(辗转相除法),1特判为第一个 g c d ( i 3 , i ) = i gcd(i^3,i) = i gcd(i3,i)=i即可,
- 怎么求 g ( n ) = ∑ i = 1 i = n g c d ( i , n ) g(n) = \sum_{i= 1}^{i = n}gcd(i,n) g(n)=∑i=1i=ngcd(i,n),我们发现 g ( n ) g(n) g(n)符合积性函数的性质, g ( p i ) = ( i + 1 ) ∗ p i + p i − 1 g(p^i)=(i+1)*p^i+p^{i-1} g(pi)=(i+1)∗pi+pi−1,线性筛预处理即可
- 考虑最后一块 ∑ j = i 3 n g c d ( j , i ) \sum_{j = i^3}^{n}gcd(j,i) ∑j=i3ngcd(j,i),对其按照i进行分块,不足一块的部分 ≤ 1 e 7 \leq1e7 ≤1e7, 可以利用欧拉函数暴力算出来。
https://github.com/Strive-for-excellence/Training/blob/master/2019%20Multi-University%20Training%20Contest%201/1011.cpp
1013 Code
题意: 询问是否存在一条直线将黑白点分开
分析: 对黑白点分别求凸包,然后特判只有一个点,两个点的情况,具体实现看代码
代码参考
https://github.com/Strive-for-excellence/Training/blob/master/2019%20Multi-University%20Training%20Contest%201/1013.cpp