A组
糖果:重复覆盖模型
重复覆盖问题 和 精确覆盖问题
解法:十字链表dancing links
转化成,最小公共集合问题
优化方式:
- 迭代加深
- 找选择最少的列
- 可行性剪枝(估计函数h=最少需要选多少行)
1和3 的组合就是A*
非优化版本
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110, M = 1 << 20;
int n, m, k;
vector<int> col[N];
int log2[M];
int lowbit(int x) // 返回末尾的1
{
return x & -x;
}
bool dfs(int depth, int state)
{
if (!depth) return state == (1 << m) - 1;
int t = -1;
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
if (t == -1 || col[t].size() > col[c].size())
t = c;
}
for (auto row : col[t])
if (dfs(depth - 1, state | row))
return true;
return false;
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
for (int i = 0; i < n; i ++ )
{
int state = 0;
for (int j = 0; j < k; j ++ )
{
int c;
scanf("%d", &c);
state |= 1 << c - 1;
}
for (int j = 0; j < m; j ++ )
if (state >> j & 1)
col[j].push_back(state);
}
int depth = 0;
while (depth <= m && !dfs(depth, 0)) depth ++ ; // 没有全选并且还不能凑齐
if (depth > m) depth = -1;
cout << depth << endl;
return 0;
}
优化版本
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110, M = 1 << 20;
int n, m, k;
vector<int> col[N];
int log2[M];
int lowbit(int x) // 返回末尾的1
{
return x & -x;
}
int h(int state) // 至少需要再选几行
{
int res = 0;
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
res ++ ;
for (auto row : col[c]) i &= ~row;
}
return res;
}
bool dfs(int depth, int state)
{
if (!depth || h(state) > depth) return state == (1 << m) - 1;
int t = -1;
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
if (t == -1 || col[t].size() > col[c].size())
t = c;
}
for (auto row : col[t])
if (dfs(depth - 1, state | row))
return true;
return false;
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
for (int i = 0; i < n; i ++ )
{
int state = 0;
for (int j = 0; j < k; j ++ )
{
int c;
scanf("%d", &c);
state |= 1 << c - 1;
}
for (int j = 0; j < m; j ++ )
if (state >> j & 1)
col[j].push_back(state);
}
int depth = 0;
while (depth <= m && !dfs(depth, 0)) depth ++ ; // 没有全选并且还不能凑齐
if (depth > m) depth = -1;
cout << depth << endl;
return 0;
}
组合数问题:数位DP(表示不会)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1E9 +7;
ll t, k;
ll n, m, ans;
ll a[70], b[70];
ll dp[70][4];
int lena, lenb;
ll ans0(ll x, ll y) {
if (x < 0 || y < 0) return 0;
if (x <= y) return (x % MOD + 2) * (x % MOD + 1) / 2 % MOD;
x %= MOD, y %= MOD;
return ((y + 2) * (y + 1) / 2 % MOD + (x - y) * (y + 1) % MOD) % MOD;
}
ll ans1(ll x, ll y) {
// if (x < 0 || y < 0) return 0;
return min(x, y) + 1;
}
ll ans2(ll x, ll y) {
// if (x < 0 || y < 0) return 0;
return max(x - y + 1, 0LL);
}
int main() {
cin >> t >> k;
while (t--) {
cin >> n >> m;
if (m > n) m = n;
ans = ans0(n, m);
memset(b, 0, sizeof b);
for (lena = 0; n; lena++, n /= k) a[lena] = n % k;
for (lenb = 0; m; lenb++, m /= k) b[lenb] = m % k;
n = lena;
// memset(dp, 0, sizeof dp);
dp[n][0] = dp[n][1] = dp[n][2] = 0;
dp[n][3] = 1;
for (int i = n - 1; i >= 0; i--) {
dp[i][0] = dp[i + 1][0] * ans0(k - 1, k - 1);
dp[i][0] += dp[i + 1][1] * ans0(a[i] - 1, k - 1) + dp[i + 1][2] * ans0(k - 1,b[i] - 1);
dp[i][0] += dp[i + 1][3] * ans0(a[i] - 1, b[i] - 1);
dp[i][1] = dp[i + 1][1] * ans1(a[i], k - 1) + dp[i + 1][3] * ans1(a[i], b[i] - 1);
dp[i][2] = dp[i + 1][2] * ans2(k - 1, b[i]) + dp[i + 1][3] * ans2(a[i] - 1, b[i]);
dp[i][3] = ((a[i] >= b[i]) && dp[i + 1][3]);
dp[i][0] %= MOD; dp[i][1] %= MOD; dp[i][2] %= MOD;
}
ans -= dp[0][0] + dp[0][1] + dp[0][2] + dp[0][3];
ans = (ans % MOD + MOD) % MOD;
cout << ans << endl;
}
return 0;
}
B组
平面切分
#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#define x first
#define y second
using namespace std;
const int N = 1010;
typedef pair<long double, long double> PDD;
typedef long long LL;
LL ans;
PDD p;
long double s[N][2];
bool st[N];
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i ++ )
{
cin >> s[i][0] >> s[i][1];
set<PDD> S;
for (int j = 0; j < i; j ++ )
{
if (st[j]) continue;
if (s[i][0] == s[j][0])
{
if (s[i][1] == s[j][1])
{
st[i] = true;
break;
}
else continue;
}
p.x = (s[j][1] - s[i][1]) / (s[i][0] - s[j][0]);
p.y = s[i][0] * p.x + s[i][1];
S.insert(p);
}
if (!st[i]) ans += S.size() + 1;
}
cout << ans + 1 << endl;
return 0;
}
字串排序
import java.util.Scanner;
public class Main {
static int list[]={//存放后缀序列,这样插和删除很容易
0,0,0,0,0,//注cccbba=1,2,3,0,……
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0
};
static int[] str=new int[300];//存放前缀序列
static void reset() {//后缀序列清零
int i=0;
while(i<26&&list[i]!=0) {
list[i]=0;
++i;
}
}
static int getrnum() {//计算逆序数(分三步)
int cnt=0;
for(int i=0;str[i]!=0;++i) {//前缀的逆序数
for(int j=i;str[j]!=0;++j) {
if(str[i]>str[j]) {
++cnt;
}
}
}
for(int i=0;str[i]!=0;++i) {//前缀对后缀的逆序数
for(int j=25;j>=0;--j) {
if(str[i]-'a'>j) {
cnt+=list[j];
}
}
}
int temp=0;
for(int i=0;i<26;++i) {//后缀的逆序数
cnt+=temp*list[i];
temp+=list[i];
}
return cnt;
}
static int getinc(int c) {//获得最大逆序增量(特殊步骤中代替求逆序数函数用来提速)(可以认为在数字符串里有多少非c(传入的参数)字符)(也就是插入c逆序数能增加多少)
int i=0,cnt=0;
while(str[i]!=0) {
if(str[i]>(c+'a')) {
cnt++;
}
++i;
}
for(i=0;i<26;++i) {
if(i!=c) {
cnt+=list[i];
}
}
return cnt;
}
static void set() {//在后部序列中插入元素,保证逆序数最大
int max=0,temp=0,index=0;
for(int i=0;i<26;++i) {
list[i]++;
if((temp=getinc(i))>max) {//找出使逆序数增得最快的字符插入(这里比用增而直接记录逆序数不影响结果,但慢一些,数据10000左右要5秒左右,会超时的,不然我也不会编这么个对于的函数。。)
index=i;
max=temp;
}
list[i]--;
}
list[index]++;
}
static void getMaxStr(int l) {//获取前缀确定且长度确定的前提下的最大逆序数字串
reset();
for(int i=0;str[i]!=0;++i,--l);
while(l>0) {
set();
--l;
}
}
static void printstr() {//打印目标字符串
String Str="";
int i=0;
while(str[i]!=0) {
Str+=(char)str[i];
++i;
}
for(i=25;i>=0;--i) {//这里其实没用,既然不执行也不会影响效率,留着吧,后缀最后是空的,但曾经存在过。。。
for(int j=0;j<list[i];++j) {
Str+=(char)(i+'a');
}
}
System.out.println(Str);
}
static void getans(int num,int l) {//l是字串长度
for(int i=0;i<l;++i) {
for(int j=0;j<26;++j) {//每个位从a开始试
str[i]=j+'a';
getMaxStr(l);//获取指定前缀最大逆字串
if(getrnum()>=num) {//超了就下一个
break;
}
}
}
}
public static void main(String[] args){//这了很简洁了
int num;
Scanner sc = new Scanner(System.in);
num=sc.nextInt();//获取输入
sc.close();
int l=0;
while(getrnum()<num) {//获取最短字串长
++l;
getMaxStr(l);
}
getans(num,l);//获得目标字串
printstr();//打印
}
}
C组
左孩子右兄弟
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 100010;
vector<int> g[N];
int n;
int dfs(int u)
{
int ans = 0;
int cnt = g[u].size();
for (int i = 0; i < cnt; i ++ )
{
ans = max(ans, dfs(g[u][i]));
}
return ans + cnt;
}
int main()
{
cin >> n;
for (int i = 2; i <= n; i ++ )
{
int x;
scanf("%d", &x);
g[x].push_back(i);
}
int ans = dfs(1);
cout << ans << endl;
return 0;
}
括号序列
合法括号序列的性质:
- 左右括号相同
- 任何一个前缀中,左括号数>=右括号数量
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010, MOD = 1e9 + 7;
typedef long long LL;
int n;
char s[N];
LL f[N][N];
LL work()
{
memset(f, 0, sizeof f);
f[0][0] = 1; // 不选择字符时,左括号比右括号多0,
// 这个时候不添加左括号也是一种方案方案数为1
for (int i = 1; i <= n; i ++ )
{
if (s[i] == '(')
{
for (int j = 1; j <= n; j ++ )
f[i][j] = f[i - 1][j - 1];
}
else
{
f[i][0] = (f[i - 1][1] + f[i - 1][0]) % MOD;
for (int j = 1; j <= n; j ++ )
{
f[i][j] = (f[i - 1][j + 1] + f[i][j - 1]) % MOD;
}
}
}
for (int i = 0; i <= n; i ++ )
if (f[n][i])
return f[n][i];
return -1;
}
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
LL l = work();
reverse(s + 1, s + n + 1);
for (int i = 1; i <= n; i ++ )
{
if (s[i] == '(') s[i] = ')';
else s[i] = '(';
}
LL r = work();
printf("%lld", (l * r) % MOD);
return 0;
}